diff --git a/Directory.Packages.props b/Directory.Packages.props
index e60b2c718a..8bedbf588a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -47,10 +47,10 @@
-
+
-
+
@@ -61,7 +61,7 @@
-
+
@@ -75,12 +75,12 @@
-
+
-
+
@@ -91,7 +91,6 @@
-
diff --git a/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs
index 572a747179..0128ef6ada 100644
--- a/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs
+++ b/src/Umbraco.Cms.Api.Common/Configuration/ConfigureUmbracoSwaggerGenOptions.cs
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.OpenApi;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Common.Configuration;
@@ -14,22 +15,23 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions apiVersioningOptions,
- IOperationIdSelector operationIdSelector,
- ISchemaIdSelector schemaIdSelector)
- : this(operationIdSelector, schemaIdSelector)
- {
- }
-
+ [Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16.")]
public ConfigureUmbracoSwaggerGenOptions(
IOperationIdSelector operationIdSelector,
ISchemaIdSelector schemaIdSelector)
+ : this(operationIdSelector, schemaIdSelector, StaticServiceProvider.Instance.GetRequiredService())
+ { }
+
+ public ConfigureUmbracoSwaggerGenOptions(
+ IOperationIdSelector operationIdSelector,
+ ISchemaIdSelector schemaIdSelector,
+ ISubTypesSelector subTypesSelector)
{
_operationIdSelector = operationIdSelector;
_schemaIdSelector = schemaIdSelector;
+ _subTypesSelector = subTypesSelector;
}
public void Configure(SwaggerGenOptions swaggerGenOptions)
@@ -62,6 +64,7 @@ public class ConfigureUmbracoSwaggerGenOptions : IConfigureOptions();
swaggerGenOptions.CustomSchemaIds(_schemaIdSelector.SchemaId);
+ swaggerGenOptions.SelectSubTypesUsing(_subTypesSelector.SubTypes);
swaggerGenOptions.SupportNonNullableReferenceTypes();
}
diff --git a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderApiExtensions.cs b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderApiExtensions.cs
index 49fe1f233e..fcd7d9c6ec 100644
--- a/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderApiExtensions.cs
+++ b/src/Umbraco.Cms.Api.Common/DependencyInjection/UmbracoBuilderApiExtensions.cs
@@ -23,6 +23,8 @@ public static class UmbracoBuilderApiExtensions
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
builder.Services.Configure(options => options.AddFilter(new SwaggerRouteTemplatePipelineFilter("UmbracoApiCommon")));
return builder;
diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/ISubTypesHandler.cs b/src/Umbraco.Cms.Api.Common/OpenApi/ISubTypesHandler.cs
new file mode 100644
index 0000000000..4a6ca4109a
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Common/OpenApi/ISubTypesHandler.cs
@@ -0,0 +1,8 @@
+namespace Umbraco.Cms.Api.Common.OpenApi;
+
+public interface ISubTypesHandler
+{
+ bool CanHandle(Type type, string documentName);
+
+ IEnumerable Handle(Type type);
+}
diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/ISubTypesSelector.cs b/src/Umbraco.Cms.Api.Common/OpenApi/ISubTypesSelector.cs
new file mode 100644
index 0000000000..164b6196aa
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Common/OpenApi/ISubTypesSelector.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Api.Common.OpenApi;
+
+public interface ISubTypesSelector
+{
+ IEnumerable SubTypes(Type type);
+}
diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/SubTypesHandler.cs b/src/Umbraco.Cms.Api.Common/OpenApi/SubTypesHandler.cs
new file mode 100644
index 0000000000..ffba71aaf8
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Common/OpenApi/SubTypesHandler.cs
@@ -0,0 +1,20 @@
+using Umbraco.Cms.Api.Common.Serialization;
+
+namespace Umbraco.Cms.Api.Common.OpenApi;
+
+public class SubTypesHandler : ISubTypesHandler
+{
+ private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
+
+ public SubTypesHandler(IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
+ => _umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
+
+ protected virtual bool CanHandle(Type type)
+ => type.Namespace?.StartsWith("Umbraco.Cms") is true;
+
+ public virtual bool CanHandle(Type type, string documentName)
+ => CanHandle(type);
+
+ public virtual IEnumerable Handle(Type type)
+ => _umbracoJsonTypeInfoResolver.FindSubTypes(type);
+}
diff --git a/src/Umbraco.Cms.Api.Common/OpenApi/SubTypesSelector.cs b/src/Umbraco.Cms.Api.Common/OpenApi/SubTypesSelector.cs
new file mode 100644
index 0000000000..c6e172f3f7
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Common/OpenApi/SubTypesSelector.cs
@@ -0,0 +1,60 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Api.Common.Serialization;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.Hosting;
+using Umbraco.Extensions;
+
+namespace Umbraco.Cms.Api.Common.OpenApi;
+
+public class SubTypesSelector : ISubTypesSelector
+{
+ private readonly IOptions _settings;
+ private readonly IHostingEnvironment _hostingEnvironment;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly IEnumerable _subTypeHandlers;
+ private readonly IUmbracoJsonTypeInfoResolver _umbracoJsonTypeInfoResolver;
+
+ public SubTypesSelector(
+ IOptions settings,
+ IHostingEnvironment hostingEnvironment,
+ IHttpContextAccessor httpContextAccessor,
+ IEnumerable subTypeHandlers,
+ IUmbracoJsonTypeInfoResolver umbracoJsonTypeInfoResolver)
+ {
+ _settings = settings;
+ _hostingEnvironment = hostingEnvironment;
+ _httpContextAccessor = httpContextAccessor;
+ _subTypeHandlers = subTypeHandlers;
+ _umbracoJsonTypeInfoResolver = umbracoJsonTypeInfoResolver;
+ }
+
+ public IEnumerable SubTypes(Type type)
+ {
+ var backOfficePath = _settings.Value.GetBackOfficePath(_hostingEnvironment);
+ var swaggerPath = $"{backOfficePath}/swagger";
+
+ if (_httpContextAccessor.HttpContext?.Request.Path.StartsWithSegments(swaggerPath) ?? false)
+ {
+ // Split the path into segments
+ var segments = _httpContextAccessor.HttpContext.Request.Path.Value!
+ .Substring(swaggerPath.Length)
+ .TrimStart(Constants.CharArrays.ForwardSlash)
+ .Split(Constants.CharArrays.ForwardSlash);
+
+ // Extract the document name from the path
+ var documentName = segments[0];
+
+ // Find the first handler that can handle the type / document name combination
+ ISubTypesHandler? handler = _subTypeHandlers.FirstOrDefault(h => h.CanHandle(type, documentName));
+ if (handler != null)
+ {
+ return handler.Handle(type);
+ }
+ }
+
+ // Default implementation to maintain backwards compatibility
+ return _umbracoJsonTypeInfoResolver.FindSubTypes(type);
+ }
+}
diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdMediaApiController.cs
index 76ce80898f..f7fa2f6d92 100644
--- a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdMediaApiController.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdMediaApiController.cs
@@ -12,8 +12,10 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Media;
[ApiVersion("2.0")]
public class ByIdMediaApiController : MediaApiControllerBase
{
- public ByIdMediaApiController(IPublishedSnapshotAccessor publishedSnapshotAccessor, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
- : base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
+ public ByIdMediaApiController(
+ IPublishedMediaCache publishedMediaCache,
+ IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
+ : base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
{
}
diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdsMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdsMediaApiController.cs
index 8a3f4c7ceb..957c982c38 100644
--- a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdsMediaApiController.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByIdsMediaApiController.cs
@@ -13,8 +13,8 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Media;
[ApiVersion("2.0")]
public class ByIdsMediaApiController : MediaApiControllerBase
{
- public ByIdsMediaApiController(IPublishedSnapshotAccessor publishedSnapshotAccessor, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
- : base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
+ public ByIdsMediaApiController(IPublishedMediaCache publishedMediaCache, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
+ : base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
{
}
diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByPathMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByPathMediaApiController.cs
index ed5dc90187..0afedddffb 100644
--- a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByPathMediaApiController.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/ByPathMediaApiController.cs
@@ -16,10 +16,10 @@ public class ByPathMediaApiController : MediaApiControllerBase
private readonly IApiMediaQueryService _apiMediaQueryService;
public ByPathMediaApiController(
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
+ IPublishedMediaCache publishedMediaCache,
IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder,
IApiMediaQueryService apiMediaQueryService)
- : base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
+ : base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
=> _apiMediaQueryService = apiMediaQueryService;
[HttpGet("item/{*path}")]
diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/MediaApiControllerBase.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/MediaApiControllerBase.cs
index 5a9bc4763e..7807290cc4 100644
--- a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/MediaApiControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/MediaApiControllerBase.cs
@@ -20,18 +20,15 @@ namespace Umbraco.Cms.Api.Delivery.Controllers.Media;
public abstract class MediaApiControllerBase : DeliveryApiControllerBase
{
private readonly IApiMediaWithCropsResponseBuilder _apiMediaWithCropsResponseBuilder;
- private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
- private IPublishedMediaCache? _publishedMediaCache;
+ private IPublishedMediaCache _publishedMediaCache;
- protected MediaApiControllerBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
+ protected MediaApiControllerBase(IPublishedMediaCache publishedMediaCache, IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder)
{
- _publishedSnapshotAccessor = publishedSnapshotAccessor;
+ _publishedMediaCache = publishedMediaCache;
_apiMediaWithCropsResponseBuilder = apiMediaWithCropsResponseBuilder;
}
- protected IPublishedMediaCache PublishedMediaCache => _publishedMediaCache
- ??= _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Media
- ?? throw new InvalidOperationException("Could not obtain the published media cache");
+ protected IPublishedMediaCache PublishedMediaCache => _publishedMediaCache;
protected IApiMediaWithCropsResponse BuildApiMediaWithCrops(IPublishedContent media)
=> _apiMediaWithCropsResponseBuilder.Build(media);
diff --git a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/QueryMediaApiController.cs b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/QueryMediaApiController.cs
index de872e5d86..c61aaf8764 100644
--- a/src/Umbraco.Cms.Api.Delivery/Controllers/Media/QueryMediaApiController.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Controllers/Media/QueryMediaApiController.cs
@@ -21,10 +21,10 @@ public class QueryMediaApiController : MediaApiControllerBase
private readonly IApiMediaQueryService _apiMediaQueryService;
public QueryMediaApiController(
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
+ IPublishedMediaCache publishedMediaCache,
IApiMediaWithCropsResponseBuilder apiMediaWithCropsResponseBuilder,
IApiMediaQueryService apiMediaQueryService)
- : base(publishedSnapshotAccessor, apiMediaWithCropsResponseBuilder)
+ : base(publishedMediaCache, apiMediaWithCropsResponseBuilder)
=> _apiMediaQueryService = apiMediaQueryService;
[HttpGet]
diff --git a/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs b/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs
index d4d63acf2b..1576d0037f 100644
--- a/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Querying/QueryOptionBase.cs
@@ -7,14 +7,15 @@ namespace Umbraco.Cms.Api.Delivery.Querying;
public abstract class QueryOptionBase
{
- private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
+ private readonly IPublishedContentCache _publishedContentCache;
private readonly IRequestRoutingService _requestRoutingService;
+
public QueryOptionBase(
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
+ IPublishedContentCache publishedContentCache,
IRequestRoutingService requestRoutingService)
{
- _publishedSnapshotAccessor = publishedSnapshotAccessor;
+ _publishedContentCache = publishedContentCache;
_requestRoutingService = requestRoutingService;
}
@@ -30,11 +31,9 @@ public abstract class QueryOptionBase
return id;
}
- IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
-
// Check if the passed value is a path of a content item
var contentRoute = _requestRoutingService.GetContentRoute(queryStringValue);
- IPublishedContent? contentItem = publishedSnapshot.Content?.GetByRoute(contentRoute);
+ IPublishedContent? contentItem = _publishedContentCache.GetByRoute(contentRoute);
return contentItem?.Key;
}
diff --git a/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/AncestorsSelector.cs b/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/AncestorsSelector.cs
index 5e8e7e2019..dfb91cabb2 100644
--- a/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/AncestorsSelector.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/AncestorsSelector.cs
@@ -1,19 +1,35 @@
+using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Delivery.Indexing.Selectors;
using Umbraco.Cms.Core.DeliveryApi;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
+using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Delivery.Querying.Selectors;
public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
{
+ private readonly IPublishedContentCache _publishedContentCache;
+ private readonly IDocumentNavigationQueryService _navigationQueryService;
private const string AncestorsSpecifier = "ancestors:";
- private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
- public AncestorsSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService)
- : base(publishedSnapshotAccessor, requestRoutingService) =>
- _publishedSnapshotAccessor = publishedSnapshotAccessor;
+ public AncestorsSelector(
+ IPublishedContentCache publishedContentCache,
+ IRequestRoutingService requestRoutingService,
+ IDocumentNavigationQueryService navigationQueryService)
+ : base(publishedContentCache, requestRoutingService)
+ {
+ _publishedContentCache = publishedContentCache;
+ _navigationQueryService = navigationQueryService;
+ }
+
+ [Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
+ public AncestorsSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
+ : this(publishedContentCache, requestRoutingService, StaticServiceProvider.Instance.GetRequiredService())
+ {
+ }
///
public bool CanHandle(string query)
@@ -37,12 +53,10 @@ public sealed class AncestorsSelector : QueryOptionBase, ISelectorHandler
};
}
- IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
-
- IPublishedContent contentItem = publishedSnapshot.Content?.GetById((Guid)id)
+ IPublishedContent contentItem = _publishedContentCache.GetById((Guid)id)
?? throw new InvalidOperationException("Could not obtain the content cache");
- var ancestorKeys = contentItem.Ancestors().Select(a => a.Key.ToString("D")).ToArray();
+ var ancestorKeys = contentItem.Ancestors(_publishedContentCache, _navigationQueryService).Select(a => a.Key.ToString("D")).ToArray();
return new SelectorOption
{
diff --git a/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/ChildrenSelector.cs b/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/ChildrenSelector.cs
index 838b5da776..9392ce8e02 100644
--- a/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/ChildrenSelector.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/ChildrenSelector.cs
@@ -9,8 +9,8 @@ public sealed class ChildrenSelector : QueryOptionBase, ISelectorHandler
{
private const string ChildrenSpecifier = "children:";
- public ChildrenSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService)
- : base(publishedSnapshotAccessor, requestRoutingService)
+ public ChildrenSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
+ : base(publishedContentCache, requestRoutingService)
{
}
diff --git a/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/DescendantsSelector.cs b/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/DescendantsSelector.cs
index e3c9bf33fd..2a7512746e 100644
--- a/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/DescendantsSelector.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Querying/Selectors/DescendantsSelector.cs
@@ -9,8 +9,8 @@ public sealed class DescendantsSelector : QueryOptionBase, ISelectorHandler
{
private const string DescendantsSpecifier = "descendants:";
- public DescendantsSelector(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestRoutingService requestRoutingService)
- : base(publishedSnapshotAccessor, requestRoutingService)
+ public DescendantsSelector(IPublishedContentCache publishedContentCache, IRequestRoutingService requestRoutingService)
+ : base(publishedContentCache, requestRoutingService)
{
}
diff --git a/src/Umbraco.Cms.Api.Delivery/Services/ApiMediaQueryService.cs b/src/Umbraco.Cms.Api.Delivery/Services/ApiMediaQueryService.cs
index 8a078d7f0d..7979895ba3 100644
--- a/src/Umbraco.Cms.Api.Delivery/Services/ApiMediaQueryService.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Services/ApiMediaQueryService.cs
@@ -4,6 +4,7 @@ using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
+using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Extensions;
@@ -12,13 +13,15 @@ namespace Umbraco.Cms.Api.Delivery.Services;
///
internal sealed class ApiMediaQueryService : IApiMediaQueryService
{
- private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
+ private readonly IPublishedMediaCache _publishedMediaCache;
private readonly ILogger _logger;
+ private readonly IMediaNavigationQueryService _mediaNavigationQueryService;
- public ApiMediaQueryService(IPublishedSnapshotAccessor publishedSnapshotAccessor, ILogger logger)
+ public ApiMediaQueryService(IPublishedMediaCache publishedMediaCache, ILogger logger, IMediaNavigationQueryService mediaNavigationQueryService)
{
- _publishedSnapshotAccessor = publishedSnapshotAccessor;
+ _publishedMediaCache = publishedMediaCache;
_logger = logger;
+ _mediaNavigationQueryService = mediaNavigationQueryService;
}
///
@@ -52,8 +55,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
=> TryGetByPath(path, GetRequiredPublishedMediaCache());
private IPublishedMediaCache GetRequiredPublishedMediaCache()
- => _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Media
- ?? throw new InvalidOperationException("Could not obtain the published media cache");
+ => _publishedMediaCache;
private IPublishedContent? TryGetByPath(string path, IPublishedMediaCache mediaCache)
{
@@ -69,7 +71,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
break;
}
- currentChildren = resolvedMedia.Children;
+ currentChildren = resolvedMedia.Children(null, _publishedMediaCache, _mediaNavigationQueryService);
}
return resolvedMedia;
@@ -102,7 +104,7 @@ internal sealed class ApiMediaQueryService : IApiMediaQueryService
? mediaCache.GetById(parentKey)
: TryGetByPath(childrenOf, mediaCache);
- return parent?.Children ?? Array.Empty();
+ return parent?.Children(null, _publishedMediaCache, _mediaNavigationQueryService) ?? Array.Empty();
}
private IEnumerable? ApplyFilters(IEnumerable source, IEnumerable filters)
diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestRedirectService.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestRedirectService.cs
index 4b9efc03a5..2f74d86364 100644
--- a/src/Umbraco.Cms.Api.Delivery/Services/RequestRedirectService.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Services/RequestRedirectService.cs
@@ -21,7 +21,7 @@ internal sealed class RequestRedirectService : RoutingServiceBase, IRequestRedir
private readonly GlobalSettings _globalSettings;
public RequestRedirectService(
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
+ IDomainCache domainCache,
IHttpContextAccessor httpContextAccessor,
IRequestStartItemProviderAccessor requestStartItemProviderAccessor,
IRequestCultureService requestCultureService,
@@ -29,7 +29,7 @@ internal sealed class RequestRedirectService : RoutingServiceBase, IRequestRedir
IApiPublishedContentCache apiPublishedContentCache,
IApiContentRouteBuilder apiContentRouteBuilder,
IOptions globalSettings)
- : base(publishedSnapshotAccessor, httpContextAccessor, requestStartItemProviderAccessor)
+ : base(domainCache, httpContextAccessor, requestStartItemProviderAccessor)
{
_requestCultureService = requestCultureService;
_redirectUrlService = redirectUrlService;
diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestRoutingService.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestRoutingService.cs
index 6bf0dbc887..67cf9c9fc0 100644
--- a/src/Umbraco.Cms.Api.Delivery/Services/RequestRoutingService.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Services/RequestRoutingService.cs
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core.DeliveryApi;
+using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
@@ -12,11 +13,11 @@ internal sealed class RequestRoutingService : RoutingServiceBase, IRequestRoutin
private readonly IRequestCultureService _requestCultureService;
public RequestRoutingService(
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
+ IDomainCache domainCache,
IHttpContextAccessor httpContextAccessor,
IRequestStartItemProviderAccessor requestStartItemProviderAccessor,
IRequestCultureService requestCultureService)
- : base(publishedSnapshotAccessor, httpContextAccessor, requestStartItemProviderAccessor) =>
+ : base(domainCache, httpContextAccessor, requestStartItemProviderAccessor) =>
_requestCultureService = requestCultureService;
///
diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs
index dd72d930bd..e79a2cbdd7 100644
--- a/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Services/RequestStartItemProvider.cs
@@ -3,29 +3,34 @@ using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
+using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Delivery.Services;
internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestStartItemProvider
{
- private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IRequestPreviewService _requestPreviewService;
+ private readonly IDocumentNavigationQueryService _documentNavigationQueryService;
+ private readonly IPublishedContentCache _publishedContentCache;
// this provider lifetime is Scope, so we can cache this as a field
private IPublishedContent? _requestedStartContent;
public RequestStartItemProvider(
IHttpContextAccessor httpContextAccessor,
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
IVariationContextAccessor variationContextAccessor,
- IRequestPreviewService requestPreviewService)
+ IRequestPreviewService requestPreviewService,
+ IDocumentNavigationQueryService documentNavigationQueryService,
+ IPublishedContentCache publishedContentCache)
: base(httpContextAccessor)
{
- _publishedSnapshotAccessor = publishedSnapshotAccessor;
+
_variationContextAccessor = variationContextAccessor;
_requestPreviewService = requestPreviewService;
+ _documentNavigationQueryService = documentNavigationQueryService;
+ _publishedContentCache = publishedContentCache;
}
///
@@ -42,13 +47,11 @@ internal sealed class RequestStartItemProvider : RequestHeaderHandler, IRequestS
return null;
}
- if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) == false ||
- publishedSnapshot?.Content == null)
- {
- return null;
- }
-
- IEnumerable rootContent = publishedSnapshot.Content.GetAtRoot(_requestPreviewService.IsPreview());
+ _documentNavigationQueryService.TryGetRootKeys(out IEnumerable rootKeys);
+ IEnumerable rootContent = rootKeys
+ .Select(_publishedContentCache.GetById)
+ .WhereNotNull()
+ .Where(x => x.IsPublished() != _requestPreviewService.IsPreview());
_requestedStartContent = Guid.TryParse(headerValue, out Guid key)
? rootContent.FirstOrDefault(c => c.Key == key)
diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs b/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs
index 32a8affd61..4a05521b22 100644
--- a/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs
+++ b/src/Umbraco.Cms.Api.Delivery/Services/RoutingServiceBase.cs
@@ -9,16 +9,16 @@ namespace Umbraco.Cms.Api.Delivery.Services;
internal abstract class RoutingServiceBase
{
- private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
+ private readonly IDomainCache _domainCache;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IRequestStartItemProviderAccessor _requestStartItemProviderAccessor;
protected RoutingServiceBase(
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
+ IDomainCache domainCache,
IHttpContextAccessor httpContextAccessor,
IRequestStartItemProviderAccessor requestStartItemProviderAccessor)
{
- _publishedSnapshotAccessor = publishedSnapshotAccessor;
+ _domainCache = domainCache;
_httpContextAccessor = httpContextAccessor;
_requestStartItemProviderAccessor = requestStartItemProviderAccessor;
}
@@ -40,15 +40,9 @@ internal abstract class RoutingServiceBase
protected DomainAndUri? GetDomainAndUriForRoute(Uri contentUrl)
{
- IDomainCache? domainCache = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Domains;
- if (domainCache == null)
- {
- throw new InvalidOperationException("Could not obtain the domain cache in the current context");
- }
+ IEnumerable domains = _domainCache.GetAll(false);
- IEnumerable domains = domainCache.GetAll(false);
-
- return DomainUtilities.SelectDomain(domains, contentUrl, defaultCulture: domainCache.DefaultCulture);
+ return DomainUtilities.SelectDomain(domains, contentUrl, defaultCulture: _domainCache.DefaultCulture);
}
protected IPublishedContent? GetStartItem()
diff --git a/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs b/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs
index 6b67c805af..74862e3bab 100644
--- a/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs
+++ b/src/Umbraco.Cms.Api.Management/Configuration/ConfigureUmbracoManagementApiSwaggerGenOptions.cs
@@ -31,7 +31,6 @@ public class ConfigureUmbracoManagementApiSwaggerGenOptions : IConfigureOptions<
});
swaggerGenOptions.OperationFilter();
- swaggerGenOptions.SelectSubTypesUsing(_umbracoJsonTypeInfoResolver.FindSubTypes);
swaggerGenOptions.UseOneOfForPolymorphism();
// Ensure all types that implements the IOpenApiDiscriminator have a $type property in the OpenApi schema with the default value (The class name) that is expected by the server
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/CollectPublishedCacheController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/CollectPublishedCacheController.cs
index 1337ece12a..4cbac68446 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/CollectPublishedCacheController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/CollectPublishedCacheController.cs
@@ -5,21 +5,15 @@ using Umbraco.Cms.Core.PublishedCache;
namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
+[Obsolete("This controller no longer serves a purpose")]
[ApiVersion("1.0")]
public class CollectPublishedCacheController : PublishedCacheControllerBase
{
- private readonly IPublishedSnapshotService _publishedSnapshotService;
-
- public CollectPublishedCacheController(IPublishedSnapshotService publishedSnapshotService)
- => _publishedSnapshotService = publishedSnapshotService;
-
[HttpPost("collect")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task Collect(CancellationToken cancellationToken)
{
- GC.Collect();
- await _publishedSnapshotService.CollectAsync();
return Ok();
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs
index b0e423e7e6..d48ad9fdbb 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/RebuildPublishedCacheController.cs
@@ -8,17 +8,16 @@ namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
[ApiVersion("1.0")]
public class RebuildPublishedCacheController : PublishedCacheControllerBase
{
- private readonly IPublishedSnapshotService _publishedSnapshotService;
+ private readonly IDatabaseCacheRebuilder _databaseCacheRebuilder;
- public RebuildPublishedCacheController(IPublishedSnapshotService publishedSnapshotService)
- => _publishedSnapshotService = publishedSnapshotService;
+ public RebuildPublishedCacheController(IDatabaseCacheRebuilder databaseCacheRebuilder) => _databaseCacheRebuilder = databaseCacheRebuilder;
[HttpPost("rebuild")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task Rebuild(CancellationToken cancellationToken)
{
- _publishedSnapshotService.Rebuild();
+ _databaseCacheRebuilder.Rebuild();
return await Task.FromResult(Ok());
}
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs
index e384742cb1..aad76e7dbf 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs
@@ -6,16 +6,12 @@ using Umbraco.Cms.Core.PublishedCache;
namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
[ApiVersion("1.0")]
+[Obsolete("This no longer relevant since snapshots are no longer used")]
public class StatusPublishedCacheController : PublishedCacheControllerBase
{
- private readonly IPublishedSnapshotStatus _publishedSnapshotStatus;
-
- public StatusPublishedCacheController(IPublishedSnapshotStatus publishedSnapshotStatus)
- => _publishedSnapshotStatus = publishedSnapshotStatus;
-
[HttpGet("status")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
public async Task> Status(CancellationToken cancellationToken)
- => await Task.FromResult(Ok(_publishedSnapshotStatus.GetStatus()));
+ => await Task.FromResult(Ok("Obsoleted"));
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/ExecuteTemplateQueryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/ExecuteTemplateQueryController.cs
index d74d299777..316dbdf51d 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/ExecuteTemplateQueryController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Query/ExecuteTemplateQueryController.cs
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Linq.Expressions;
+using System.Runtime.Versioning;
using System.Text;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
@@ -8,7 +9,9 @@ using Umbraco.Cms.Api.Management.ViewModels.Template.Query;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Models.TemplateQuery;
+using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Template.Query;
@@ -20,6 +23,8 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IPublishedValueFallback _publishedValueFallback;
private readonly IContentTypeService _contentTypeService;
+ private readonly IPublishedContentCache _contentCache;
+ private readonly IDocumentNavigationQueryService _documentNavigationQueryService;
private static readonly string _indent = $"{Environment.NewLine} ";
@@ -27,12 +32,16 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
IPublishedContentQuery publishedContentQuery,
IVariationContextAccessor variationContextAccessor,
IPublishedValueFallback publishedValueFallback,
- IContentTypeService contentTypeService)
+ IContentTypeService contentTypeService,
+ IPublishedContentCache contentCache,
+ IDocumentNavigationQueryService documentNavigationQueryService)
{
_publishedContentQuery = publishedContentQuery;
_variationContextAccessor = variationContextAccessor;
_publishedValueFallback = publishedValueFallback;
_contentTypeService = contentTypeService;
+ _contentCache = contentCache;
+ _documentNavigationQueryService = documentNavigationQueryService;
}
[HttpPost("execute")]
@@ -109,13 +118,13 @@ public class ExecuteTemplateQueryController : TemplateQueryControllerBase
queryExpression.Append($".ChildrenOfType(\"{model.DocumentTypeAlias}\")");
return rootContent == null
? Enumerable.Empty()
- : rootContent.ChildrenOfType(_variationContextAccessor, model.DocumentTypeAlias);
+ : rootContent.ChildrenOfType(_variationContextAccessor, _contentCache, _documentNavigationQueryService, model.DocumentTypeAlias);
}
queryExpression.Append(".Children()");
return rootContent == null
? Enumerable.Empty()
- : rootContent.Children(_variationContextAccessor);
+ : rootContent.Children(_variationContextAccessor, _contentCache, _documentNavigationQueryService);
}
private IEnumerable ApplyFiltering(IEnumerable? filters, IEnumerable contentQuery, StringBuilder queryExpression)
diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.BackOffice.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.BackOffice.cs
index c2b0b4d720..c8114a6384 100644
--- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.BackOffice.cs
+++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilder.BackOffice.cs
@@ -34,7 +34,6 @@ public static partial class UmbracoBuilderExtensions
.AddMvcAndRazor(configureMvc)
.AddWebServer()
.AddRecurringBackgroundJobs()
- .AddNuCache()
.AddUmbracoHybridCache()
.AddDistributedCache()
.AddCoreNotifications()
diff --git a/src/Umbraco.Cms.Api.Management/Factories/DocumentUrlFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DocumentUrlFactory.cs
index cee548ed5c..6d0e40e2b1 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/DocumentUrlFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/DocumentUrlFactory.cs
@@ -1,10 +1,13 @@
-using Microsoft.Extensions.Logging;
-using Umbraco.Cms.Api.Management.ViewModels.Content;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
using Umbraco.Cms.Api.Management.ViewModels.Document;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
+using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Core.Web;
using Umbraco.Extensions;
@@ -12,52 +15,17 @@ namespace Umbraco.Cms.Api.Management.Factories;
public class DocumentUrlFactory : IDocumentUrlFactory
{
- private readonly IPublishedRouter _publishedRouter;
- private readonly IUmbracoContextAccessor _umbracoContextAccessor;
- private readonly ILanguageService _languageService;
- private readonly ILocalizedTextService _localizedTextService;
- private readonly IContentService _contentService;
- private readonly IVariationContextAccessor _variationContextAccessor;
- private readonly ILoggerFactory _loggerFactory;
- private readonly UriUtility _uriUtility;
- private readonly IPublishedUrlProvider _publishedUrlProvider;
+ private readonly IDocumentUrlService _documentUrlService;
- public DocumentUrlFactory(
- IPublishedRouter publishedRouter,
- IUmbracoContextAccessor umbracoContextAccessor,
- ILanguageService languageService,
- ILocalizedTextService localizedTextService,
- IContentService contentService,
- IVariationContextAccessor variationContextAccessor,
- ILoggerFactory loggerFactory,
- UriUtility uriUtility,
- IPublishedUrlProvider publishedUrlProvider)
+
+ public DocumentUrlFactory(IDocumentUrlService documentUrlService)
{
- _publishedRouter = publishedRouter;
- _umbracoContextAccessor = umbracoContextAccessor;
- _languageService = languageService;
- _localizedTextService = localizedTextService;
- _contentService = contentService;
- _variationContextAccessor = variationContextAccessor;
- _loggerFactory = loggerFactory;
- _uriUtility = uriUtility;
- _publishedUrlProvider = publishedUrlProvider;
+ _documentUrlService = documentUrlService;
}
public async Task> CreateUrlsAsync(IContent content)
{
- IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
-
- IEnumerable urlInfos = await content.GetContentUrlsAsync(
- _publishedRouter,
- umbracoContext,
- _languageService,
- _localizedTextService,
- _contentService,
- _variationContextAccessor,
- _loggerFactory.CreateLogger(),
- _uriUtility,
- _publishedUrlProvider);
+ IEnumerable urlInfos = await _documentUrlService.ListUrlsAsync(content.Key);
return urlInfos
.Where(urlInfo => urlInfo.IsUrl)
diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Item/ItemTypeMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Item/ItemTypeMapDefinition.cs
index 9152970b36..5b58862377 100644
--- a/src/Umbraco.Cms.Api.Management/Mapping/Item/ItemTypeMapDefinition.cs
+++ b/src/Umbraco.Cms.Api.Management/Mapping/Item/ItemTypeMapDefinition.cs
@@ -48,6 +48,7 @@ public class ItemTypeMapDefinition : IMapDefinition
target.Name = source.Name ?? string.Empty;
target.Id = source.Key;
target.EditorUiAlias = source.EditorUiAlias;
+ target.EditorAlias = source.EditorAlias;
target.IsDeletable = source.IsDeletableDataType();
}
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json
index 29acbdd16a..eb912bf856 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi.json
+++ b/src/Umbraco.Cms.Api.Management/OpenApi.json
@@ -35987,6 +35987,7 @@
},
"DataTypeItemResponseModel": {
"required": [
+ "editorAlias",
"id",
"isDeletable",
"name"
@@ -36004,6 +36005,9 @@
"type": "string",
"nullable": true
},
+ "editorAlias": {
+ "type": "string"
+ },
"isDeletable": {
"type": "boolean"
}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DataType/Item/DataTypeItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/Item/DataTypeItemResponseModel.cs
index b753aa22b2..947abe388d 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/DataType/Item/DataTypeItemResponseModel.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/Item/DataTypeItemResponseModel.cs
@@ -6,5 +6,7 @@ public class DataTypeItemResponseModel : NamedItemResponseModelBase
{
public string? EditorUiAlias { get; set; }
+ public string EditorAlias { get; set; } = string.Empty;
+
public bool IsDeletable { get; set; }
}
diff --git a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj
index 2acd51abd5..e9d05b5633 100644
--- a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj
+++ b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj
@@ -9,8 +9,6 @@
IDE0270,CS0108,CS1998
-
-
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs
index 0d140feef3..24d4162da3 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs
@@ -79,6 +79,7 @@ public class SqliteSyntaxProvider : SqlSyntaxProviderBase
switch (indexTypes)
{
case IndexTypes.UniqueNonClustered:
+ case IndexTypes.UniqueClustered:
return "UNIQUE";
default:
return string.Empty;
diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs
index 4ba8edf445..7f8484fca4 100644
--- a/src/Umbraco.Core/Cache/CacheKeys.cs
+++ b/src/Umbraco.Core/Cache/CacheKeys.cs
@@ -19,4 +19,7 @@ public static class CacheKeys
public const string ContentRecycleBinCacheKey = "recycleBin_content";
public const string MediaRecycleBinCacheKey = "recycleBin_media";
+
+ public const string PreviewPropertyCacheKeyPrefix = "Cache.Property.CacheValues[D:";
+ public const string PropertyCacheKeyPrefix = "Cache.Property.CacheValues[P:";
}
diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs
index 779b22fe68..3b3eb8560e 100644
--- a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs
+++ b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs
@@ -1,3 +1,5 @@
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
@@ -6,6 +8,7 @@ using Umbraco.Cms.Core.PublishedCache;
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;
@@ -14,22 +17,34 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase
{
private readonly IDomainService _domainService;
+ private readonly IDomainCacheService _domainCacheService;
+ private readonly IDocumentUrlService _documentUrlService;
+ private readonly IDocumentNavigationQueryService _documentNavigationQueryService;
+ private readonly IDocumentNavigationManagementService _documentNavigationManagementService;
+ private readonly IContentService _contentService;
private readonly IIdKeyMap _idKeyMap;
- private readonly IPublishedSnapshotService _publishedSnapshotService;
public ContentCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
- IPublishedSnapshotService publishedSnapshotService,
IIdKeyMap idKeyMap,
IDomainService domainService,
IEventAggregator eventAggregator,
- ICacheRefresherNotificationFactory factory)
+ ICacheRefresherNotificationFactory factory,
+ IDocumentUrlService documentUrlService,
+ IDomainCacheService domainCacheService,
+ IDocumentNavigationQueryService documentNavigationQueryService,
+ IDocumentNavigationManagementService documentNavigationManagementService,
+ IContentService contentService)
: base(appCaches, serializer, eventAggregator, factory)
{
- _publishedSnapshotService = publishedSnapshotService;
_idKeyMap = idKeyMap;
_domainService = domainService;
+ _domainCacheService = domainCacheService;
+ _documentUrlService = documentUrlService;
+ _documentNavigationQueryService = documentNavigationQueryService;
+ _documentNavigationManagementService = documentNavigationManagementService;
+ _contentService = contentService;
}
#region Indirect
@@ -75,7 +90,7 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase(payload.Key));
- _idKeyMap.ClearCache(payload.Id);
+
// remove those that are in the branch
if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove))
@@ -89,6 +104,17 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase 0)
@@ -107,26 +133,144 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase new DomainCacheRefresher.JsonPayload(x.Id, DomainChangeTypes.Remove)).ToArray());
}
}
- // note: must do what's above FIRST else the repositories still have the old cached
- // content and when the PublishedCachesService is notified of changes it does not see
- // the new content...
+ base.Refresh(payloads);
+ }
- // TODO: what about this?
- // should rename it, and then, this is only for Deploy, and then, ???
- // if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache)
- // ...
- if (payloads.Any(x => x.Blueprint is false))
+ private void HandleNavigation(JsonPayload payload)
+ {
+ if (payload.Key is null)
{
- // Only notify if the payload contains actual (non-blueprint) contents
- NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads);
+ return;
+ }
+
+ if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
+ {
+ _documentNavigationManagementService.MoveToBin(payload.Key.Value);
+ _documentNavigationManagementService.RemoveFromBin(payload.Key.Value);
+ }
+
+ if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
+ {
+ _documentNavigationManagementService.RebuildAsync();
+ _documentNavigationManagementService.RebuildBinAsync();
+ }
+
+ if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshNode))
+ {
+ IContent? content = _contentService.GetById(payload.Id);
+
+ if (content is null)
+ {
+ return;
+ }
+
+ HandleNavigationForSingleContent(content);
+ }
+
+ if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch))
+ {
+ IContent? content = _contentService.GetById(payload.Id);
+
+ if (content is null)
+ {
+ return;
+ }
+
+ IEnumerable descendants = _contentService.GetPagedDescendants(content.Id, 0, int.MaxValue, out _);
+ foreach (IContent descendant in content.Yield().Concat(descendants))
+ {
+ HandleNavigationForSingleContent(descendant);
+ }
+ }
+ }
+
+ private void HandleNavigationForSingleContent(IContent content)
+ {
+ // First creation
+ if (ExistsInNavigation(content.Key) is false && ExistsInNavigationBin(content.Key) is false)
+ {
+ _documentNavigationManagementService.Add(content.Key, GetParentKey(content));
+ if (content.Trashed)
+ {
+ // If created as trashed, move to bin
+ _documentNavigationManagementService.MoveToBin(content.Key);
+ }
+ }
+ else if (ExistsInNavigation(content.Key) && ExistsInNavigationBin(content.Key) is false)
+ {
+ if (content.Trashed)
+ {
+ // It must have been trashed
+ _documentNavigationManagementService.MoveToBin(content.Key);
+ }
+ else
+ {
+ // It must have been saved. Check if parent is different
+ if (_documentNavigationQueryService.TryGetParentKey(content.Key, out var oldParentKey))
+ {
+ Guid? newParentKey = GetParentKey(content);
+ if (oldParentKey != newParentKey)
+ {
+ _documentNavigationManagementService.Move(content.Key, newParentKey);
+ }
+ }
+ }
+ }
+ else if (ExistsInNavigation(content.Key) is false && ExistsInNavigationBin(content.Key))
+ {
+ if (content.Trashed is false)
+ {
+ // It must have been restored
+ _documentNavigationManagementService.RestoreFromBin(content.Key, GetParentKey(content));
+ }
+ }
+ }
+
+ private Guid? GetParentKey(IContent content) => (content.ParentId == -1) ? null : _idKeyMap.GetKeyForId(content.ParentId, UmbracoObjectTypes.Document).Result;
+
+ private bool ExistsInNavigation(Guid contentKey) => _documentNavigationQueryService.TryGetParentKey(contentKey, out _);
+
+ private bool ExistsInNavigationBin(Guid contentKey) => _documentNavigationQueryService.TryGetParentKeyInBin(contentKey, out _);
+
+ private void HandleRouting(JsonPayload payload)
+ {
+ if(payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
+ {
+ var key = payload.Key ?? _idKeyMap.GetKeyForId(payload.Id, UmbracoObjectTypes.Document).Result;
+
+ //Note the we need to clear the navigation service as the last thing
+ if (_documentNavigationQueryService.TryGetDescendantsKeysOrSelfKeys(key, out var descendantsOrSelfKeys))
+ {
+ _documentUrlService.DeleteUrlsFromCacheAsync(descendantsOrSelfKeys).GetAwaiter().GetResult();
+ }
+ else if(_documentNavigationQueryService.TryGetDescendantsKeysOrSelfKeysInBin(key, out var descendantsOrSelfKeysInBin))
+ {
+ _documentUrlService.DeleteUrlsFromCacheAsync(descendantsOrSelfKeysInBin).GetAwaiter().GetResult();
+ }
+
+ }
+ if(payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
+ {
+ _documentUrlService.RebuildAllUrlsAsync().GetAwaiter().GetResult(); //TODO make async
+ }
+
+ if(payload.ChangeTypes.HasType(TreeChangeTypes.RefreshNode))
+ {
+ var key = payload.Key ?? _idKeyMap.GetKeyForId(payload.Id, UmbracoObjectTypes.Document).Result;
+ _documentUrlService.CreateOrUpdateUrlSegmentsAsync(key).GetAwaiter().GetResult();
+ }
+
+ if(payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch))
+ {
+ var key = payload.Key ?? _idKeyMap.GetKeyForId(payload.Id, UmbracoObjectTypes.Document).Result;
+ _documentUrlService.CreateOrUpdateUrlSegmentsWithDescendantsAsync(key).GetAwaiter().GetResult();
}
- base.Refresh(payloads);
}
// these events should never trigger
@@ -143,24 +287,6 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase
- /// Refreshes the publish snapshot service and if there are published changes ensures that partial view caches are
- /// refreshed too
- ///
- ///
- ///
- ///
- internal static void NotifyPublishedSnapshotService(IPublishedSnapshotService service, AppCaches appCaches, JsonPayload[] payloads)
- {
- service.Notify(payloads, out _, out var publishedChanged);
-
- if (payloads.Any(x => x.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) || publishedChanged)
- {
- // when a public version changes
- appCaches.ClearPartialViewCache();
- }
- }
-
// TODO (V14): Change into a record
public class JsonPayload
{
diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentTypeCacheRefresher.cs
index e1a82d6108..dba66ec1b0 100644
--- a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentTypeCacheRefresher.cs
+++ b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentTypeCacheRefresher.cs
@@ -3,7 +3,6 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Persistence.Repositories;
-using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Changes;
@@ -14,25 +13,22 @@ namespace Umbraco.Cms.Core.Cache;
public sealed class ContentTypeCacheRefresher : PayloadCacheRefresherBase
{
private readonly IContentTypeCommonRepository _contentTypeCommonRepository;
- private readonly IIdKeyMap _idKeyMap;
private readonly IPublishedModelFactory _publishedModelFactory;
- private readonly IPublishedSnapshotService _publishedSnapshotService;
+ private readonly IIdKeyMap _idKeyMap;
public ContentTypeCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
- IPublishedSnapshotService publishedSnapshotService,
- IPublishedModelFactory publishedModelFactory,
IIdKeyMap idKeyMap,
IContentTypeCommonRepository contentTypeCommonRepository,
IEventAggregator eventAggregator,
- ICacheRefresherNotificationFactory factory)
+ ICacheRefresherNotificationFactory factory,
+ IPublishedModelFactory publishedModelFactory)
: base(appCaches, serializer, eventAggregator, factory)
{
- _publishedSnapshotService = publishedSnapshotService;
- _publishedModelFactory = publishedModelFactory;
_idKeyMap = idKeyMap;
_contentTypeCommonRepository = contentTypeCommonRepository;
+ _publishedModelFactory = publishedModelFactory;
}
#region Json
@@ -115,9 +111,8 @@ public sealed class ContentTypeCacheRefresher : PayloadCacheRefresherBase
- _publishedSnapshotService.Notify(payloads));
+ // TODO: We need to clear the HybridCache of any content using the ContentType, but NOT the database cache here, and this should be done within the "WithSafeLiveFactoryReset" to ensure that the factory is locked in the meantime.
+ _publishedModelFactory.WithSafeLiveFactoryReset(() => { });
// now we can trigger the event
base.Refresh(payloads);
diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/DataTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/DataTypeCacheRefresher.cs
index 394630fa64..f28dd89ea5 100644
--- a/src/Umbraco.Core/Cache/Refreshers/Implement/DataTypeCacheRefresher.cs
+++ b/src/Umbraco.Core/Cache/Refreshers/Implement/DataTypeCacheRefresher.cs
@@ -3,8 +3,6 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Persistence.Repositories;
-using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
-using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
@@ -15,21 +13,18 @@ public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase
- _publishedSnapshotService.Notify(payloads));
+ // TODO: We need to clear the HybridCache of any content using the ContentType, but NOT the database cache here, and this should be done within the "WithSafeLiveFactoryReset" to ensure that the factory is locked in the meantime.
+ _publishedModelFactory.WithSafeLiveFactoryReset(() => { });
base.Refresh(payloads);
}
diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/DomainCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/DomainCacheRefresher.cs
index 9c5030e553..4c765cda71 100644
--- a/src/Umbraco.Core/Cache/Refreshers/Implement/DomainCacheRefresher.cs
+++ b/src/Umbraco.Core/Cache/Refreshers/Implement/DomainCacheRefresher.cs
@@ -9,17 +9,17 @@ namespace Umbraco.Cms.Core.Cache;
public sealed class DomainCacheRefresher : PayloadCacheRefresherBase
{
- private readonly IPublishedSnapshotService _publishedSnapshotService;
+ private readonly IDomainCacheService _domainCacheService;
public DomainCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
- IPublishedSnapshotService publishedSnapshotService,
IEventAggregator eventAggregator,
- ICacheRefresherNotificationFactory factory)
+ ICacheRefresherNotificationFactory factory,
+ IDomainCacheService domainCacheService)
: base(appCaches, serializer, eventAggregator, factory)
{
- _publishedSnapshotService = publishedSnapshotService;
+ _domainCacheService = domainCacheService;
}
#region Json
@@ -60,8 +60,7 @@ public sealed class DomainCacheRefresher : PayloadCacheRefresherBase
{
+ private readonly IDomainCacheService _domainCacheService;
+
public LanguageCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
- IPublishedSnapshotService publishedSnapshotService,
IEventAggregator eventAggregator,
+ IDomainCacheService domainCache,
ICacheRefresherNotificationFactory factory)
- : base(appCaches, serializer, eventAggregator, factory) =>
- _publishedSnapshotService = publishedSnapshotService;
+ : base(appCaches, serializer, eventAggregator, factory)
+ {
+ _domainCacheService = domainCache;
+ }
///
/// Clears all domain caches
@@ -34,7 +38,7 @@ public sealed class LanguageCacheRefresher : PayloadCacheRefresherBase UniqueId;
@@ -141,8 +144,6 @@ public sealed class LanguageCacheRefresher : PayloadCacheRefresherBase
{
private readonly IIdKeyMap _idKeyMap;
- private readonly IPublishedSnapshotService _publishedSnapshotService;
+ private readonly IMediaNavigationQueryService _mediaNavigationQueryService;
+ private readonly IMediaNavigationManagementService _mediaNavigationManagementService;
+ private readonly IMediaService _mediaService;
public MediaCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
- IPublishedSnapshotService publishedSnapshotService,
IIdKeyMap idKeyMap,
IEventAggregator eventAggregator,
- ICacheRefresherNotificationFactory factory)
+ ICacheRefresherNotificationFactory factory,
+ IMediaNavigationQueryService mediaNavigationQueryService,
+ IMediaNavigationManagementService mediaNavigationManagementService,
+ IMediaService mediaService)
: base(appCaches, serializer, eventAggregator, factory)
{
- _publishedSnapshotService = publishedSnapshotService;
_idKeyMap = idKeyMap;
+ _mediaNavigationQueryService = mediaNavigationQueryService;
+ _mediaNavigationManagementService = mediaNavigationManagementService;
+ _mediaService = mediaService;
}
#region Indirect
@@ -84,35 +91,128 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase(payload.Id));
+ mediaCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Key));
+
+ // remove those that are in the branch
+ if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove))
+ {
+ var pathid = "," + payload.Id + ",";
+ mediaCache.Result?.ClearOfType((_, v) => v.Path?.Contains(pathid) ?? false);
+ }
}
- // repository cache
- // it *was* done for each pathId but really that does not make sense
- // only need to do it for the current media
- mediaCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Id));
- mediaCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Key));
-
- // remove those that are in the branch
- if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove))
- {
- var pathid = "," + payload.Id + ",";
- mediaCache.Result?.ClearOfType((_, v) => v.Path?.Contains(pathid) ?? false);
- }
+ HandleNavigation(payload);
}
- _publishedSnapshotService.Notify(payloads, out var hasPublishedDataChanged);
- // we only need to clear this if the published cache has changed
- if (hasPublishedDataChanged)
- {
- AppCaches.ClearPartialViewCache();
- }
+ AppCaches.ClearPartialViewCache();
+
base.Refresh(payloads);
}
+ private void HandleNavigation(JsonPayload payload)
+ {
+ if (payload.Key is null)
+ {
+ return;
+ }
+
+ if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
+ {
+ _mediaNavigationManagementService.MoveToBin(payload.Key.Value);
+ _mediaNavigationManagementService.RemoveFromBin(payload.Key.Value);
+ }
+
+ if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
+ {
+ _mediaNavigationManagementService.RebuildAsync();
+ _mediaNavigationManagementService.RebuildBinAsync();
+ }
+
+ if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshNode))
+ {
+ IMedia? media = _mediaService.GetById(payload.Id);
+
+ if (media is null)
+ {
+ return;
+ }
+
+ HandleNavigationForSingleMedia(media);
+ }
+
+ if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch))
+ {
+ IMedia? media = _mediaService.GetById(payload.Id);
+
+ if (media is null)
+ {
+ return;
+ }
+
+ IEnumerable descendants = _mediaService.GetPagedDescendants(media.Id, 0, int.MaxValue, out _);
+ foreach (IMedia descendant in media.Yield().Concat(descendants))
+ {
+ HandleNavigationForSingleMedia(descendant);
+ }
+ }
+ }
+
+ private void HandleNavigationForSingleMedia(IMedia media)
+ {
+ // First creation
+ if (ExistsInNavigation(media.Key) is false && ExistsInNavigationBin(media.Key) is false)
+ {
+ _mediaNavigationManagementService.Add(media.Key, GetParentKey(media));
+ if (media.Trashed)
+ {
+ // If created as trashed, move to bin
+ _mediaNavigationManagementService.MoveToBin(media.Key);
+ }
+ }
+ else if (ExistsInNavigation(media.Key) && ExistsInNavigationBin(media.Key) is false)
+ {
+ if (media.Trashed)
+ {
+ // It must have been trashed
+ _mediaNavigationManagementService.MoveToBin(media.Key);
+ }
+ else
+ {
+ // It must have been saved. Check if parent is different
+ if (_mediaNavigationQueryService.TryGetParentKey(media.Key, out var oldParentKey))
+ {
+ Guid? newParentKey = GetParentKey(media);
+ if (oldParentKey != newParentKey)
+ {
+ _mediaNavigationManagementService.Move(media.Key, newParentKey);
+ }
+ }
+ }
+ }
+ else if (ExistsInNavigation(media.Key) is false && ExistsInNavigationBin(media.Key))
+ {
+ if (media.Trashed is false)
+ {
+ // It must have been restored
+ _mediaNavigationManagementService.RestoreFromBin(media.Key, GetParentKey(media));
+ }
+ }
+ }
+
+ private Guid? GetParentKey(IMedia media) => (media.ParentId == -1) ? null : _idKeyMap.GetKeyForId(media.ParentId, UmbracoObjectTypes.Media).Result;
+
+ private bool ExistsInNavigation(Guid contentKey) => _mediaNavigationQueryService.TryGetParentKey(contentKey, out _);
+
+ private bool ExistsInNavigationBin(Guid contentKey) => _mediaNavigationQueryService.TryGetParentKeyInBin(contentKey, out _);
+
+
// these events should never trigger
// everything should be JSON
public override void RefreshAll() => throw new NotSupportedException();
diff --git a/src/Umbraco.Core/Composing/CompositionExtensions.cs b/src/Umbraco.Core/Composing/CompositionExtensions.cs
deleted file mode 100644
index 2906070e4f..0000000000
--- a/src/Umbraco.Core/Composing/CompositionExtensions.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using Umbraco.Cms.Core.DependencyInjection;
-using Umbraco.Cms.Core.PublishedCache;
-
-namespace Umbraco.Extensions;
-
-public static class CompositionExtensions
-{
- ///
- /// Sets the published snapshot service.
- ///
- /// The builder.
- /// A function creating a published snapshot service.
- public static IUmbracoBuilder SetPublishedSnapshotService(
- this IUmbracoBuilder builder,
- Func factory)
- {
- builder.Services.AddUnique(factory);
- return builder;
- }
-
- ///
- /// Sets the published snapshot service.
- ///
- /// The type of the published snapshot service.
- /// The builder.
- public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder)
- where T : class, IPublishedSnapshotService
- {
- builder.Services.AddUnique();
- return builder;
- }
-
- ///
- /// Sets the published snapshot service.
- ///
- /// The builder.
- /// A published snapshot service.
- public static IUmbracoBuilder SetPublishedSnapshotService(
- this IUmbracoBuilder builder,
- IPublishedSnapshotService service)
- {
- builder.Services.AddUnique(service);
- return builder;
- }
-}
diff --git a/src/Umbraco.Core/DeliveryApi/ApiContentRouteBuilder.cs b/src/Umbraco.Core/DeliveryApi/ApiContentRouteBuilder.cs
index d33406a95a..45bb26cb0c 100644
--- a/src/Umbraco.Core/DeliveryApi/ApiContentRouteBuilder.cs
+++ b/src/Umbraco.Core/DeliveryApi/ApiContentRouteBuilder.cs
@@ -1,11 +1,9 @@
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
-using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.DeliveryApi;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
-using Umbraco.Cms.Core.Routing;
+using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.DeliveryApi;
@@ -15,47 +13,25 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
private readonly IApiContentPathProvider _apiContentPathProvider;
private readonly GlobalSettings _globalSettings;
private readonly IVariationContextAccessor _variationContextAccessor;
- private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IRequestPreviewService _requestPreviewService;
+ private readonly IPublishedContentCache _contentCache;
+ private readonly IDocumentNavigationQueryService _navigationQueryService;
private RequestHandlerSettings _requestSettings;
- [Obsolete($"Use the constructor that does not accept {nameof(IPublishedUrlProvider)}. Will be removed in V15.")]
- public ApiContentRouteBuilder(
- IPublishedUrlProvider publishedUrlProvider,
- IOptions globalSettings,
- IVariationContextAccessor variationContextAccessor,
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
- IRequestPreviewService requestPreviewService,
- IOptionsMonitor requestSettings)
- : this(StaticServiceProvider.Instance.GetRequiredService(), globalSettings, variationContextAccessor, publishedSnapshotAccessor, requestPreviewService, requestSettings)
- {
- }
-
- [Obsolete($"Use the constructor that does not accept {nameof(IPublishedUrlProvider)}. Will be removed in V15.")]
- public ApiContentRouteBuilder(
- IPublishedUrlProvider publishedUrlProvider,
- IApiContentPathProvider apiContentPathProvider,
- IOptions globalSettings,
- IVariationContextAccessor variationContextAccessor,
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
- IRequestPreviewService requestPreviewService,
- IOptionsMonitor requestSettings)
- : this(apiContentPathProvider, globalSettings, variationContextAccessor, publishedSnapshotAccessor, requestPreviewService, requestSettings)
- {
- }
-
public ApiContentRouteBuilder(
IApiContentPathProvider apiContentPathProvider,
IOptions globalSettings,
IVariationContextAccessor variationContextAccessor,
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
IRequestPreviewService requestPreviewService,
- IOptionsMonitor requestSettings)
+ IOptionsMonitor requestSettings,
+ IPublishedContentCache contentCache,
+ IDocumentNavigationQueryService navigationQueryService)
{
_apiContentPathProvider = apiContentPathProvider;
_variationContextAccessor = variationContextAccessor;
- _publishedSnapshotAccessor = publishedSnapshotAccessor;
_requestPreviewService = requestPreviewService;
+ _contentCache = contentCache;
+ _navigationQueryService = navigationQueryService;
_globalSettings = globalSettings.Value;
_requestSettings = requestSettings.CurrentValue;
requestSettings.OnChange(settings => _requestSettings = settings);
@@ -106,7 +82,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
// we can perform fallback to the content route.
if (IsInvalidContentPath(contentPath))
{
- contentPath = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Content?.GetRouteById(content.Id, culture) ?? contentPath;
+ contentPath = _contentCache.GetRouteById(content.Id, culture) ?? contentPath;
}
// if the content path has still not been resolved as a valid path, the content is un-routable in this culture
@@ -129,16 +105,15 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
{
if (isPreview is false)
{
- return content.Root();
+ return content.Root(_contentCache, _navigationQueryService);
}
+ _navigationQueryService.TryGetRootKeys(out IEnumerable rootKeys);
+ IEnumerable rootContent = rootKeys.Select(x => _contentCache.GetById(true, x)).WhereNotNull();
+
// in very edge case scenarios during preview, content.Root() does not map to the root.
// we'll code our way around it for the time being.
- return _publishedSnapshotAccessor
- .GetRequiredPublishedSnapshot()
- .Content?
- .GetAtRoot(true)
- .FirstOrDefault(root => root.IsAncestorOrSelf(content))
- ?? content.Root();
+ return rootContent.FirstOrDefault(root => root.IsAncestorOrSelf(content))
+ ?? content.Root(_contentCache, _navigationQueryService);
}
}
diff --git a/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs b/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs
index 5c138dbf7e..8d4cf4026a 100644
--- a/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs
+++ b/src/Umbraco.Core/DeliveryApi/ApiPublishedContentCache.cs
@@ -1,69 +1,84 @@
-using Microsoft.Extensions.Options;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
+using Umbraco.Cms.Core.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.DeliveryApi;
public sealed class ApiPublishedContentCache : IApiPublishedContentCache
{
- private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IRequestPreviewService _requestPreviewService;
+ private readonly IRequestCultureService _requestCultureService;
+ private readonly IDocumentUrlService _documentUrlService;
+ private readonly IPublishedContentCache _publishedContentCache;
private DeliveryApiSettings _deliveryApiSettings;
- public ApiPublishedContentCache(IPublishedSnapshotAccessor publishedSnapshotAccessor, IRequestPreviewService requestPreviewService, IOptionsMonitor deliveryApiSettings)
+ public ApiPublishedContentCache(
+ IRequestPreviewService requestPreviewService,
+ IRequestCultureService requestCultureService,
+ IOptionsMonitor deliveryApiSettings,
+ IDocumentUrlService documentUrlService,
+ IPublishedContentCache publishedContentCache)
{
- _publishedSnapshotAccessor = publishedSnapshotAccessor;
_requestPreviewService = requestPreviewService;
+ _requestCultureService = requestCultureService;
+ _documentUrlService = documentUrlService;
+ _publishedContentCache = publishedContentCache;
_deliveryApiSettings = deliveryApiSettings.CurrentValue;
deliveryApiSettings.OnChange(settings => _deliveryApiSettings = settings);
}
+
public IPublishedContent? GetByRoute(string route)
{
- IPublishedContentCache? contentCache = GetContentCache();
- if (contentCache == null)
+ var isPreviewMode = _requestPreviewService.IsPreview();
+
+
+ // Handle the nasty logic with domain document ids in front of paths.
+ int? documentStartNodeId = null;
+ if (route.StartsWith("/") is false)
{
- return null;
+ var index = route.IndexOf('/');
+
+ if (index > -1 && int.TryParse(route.Substring(0, index), out var nodeId))
+ {
+ documentStartNodeId = nodeId;
+ route = route.Substring(index);
+ }
}
- IPublishedContent? content = contentCache.GetByRoute(_requestPreviewService.IsPreview(), route);
+ Guid? documentKey = _documentUrlService.GetDocumentKeyByRoute(
+ route,
+ _requestCultureService.GetRequestedCulture(),
+ documentStartNodeId,
+ _requestPreviewService.IsPreview()
+ );
+ IPublishedContent? content = documentKey.HasValue
+ ? _publishedContentCache.GetById(isPreviewMode, documentKey.Value)
+ : null;
+
return ContentOrNullIfDisallowed(content);
}
public IPublishedContent? GetById(Guid contentId)
{
- IPublishedContentCache? contentCache = GetContentCache();
- if (contentCache == null)
- {
- return null;
- }
-
- IPublishedContent? content = contentCache.GetById(_requestPreviewService.IsPreview(), contentId);
+ IPublishedContent? content = _publishedContentCache.GetById(_requestPreviewService.IsPreview(), contentId);
return ContentOrNullIfDisallowed(content);
}
public IEnumerable GetByIds(IEnumerable contentIds)
{
- IPublishedContentCache? contentCache = GetContentCache();
- if (contentCache == null)
- {
- return Enumerable.Empty();
- }
-
return contentIds
- .Select(contentId => contentCache.GetById(_requestPreviewService.IsPreview(), contentId))
+ .Select(contentId => _publishedContentCache.GetById(_requestPreviewService.IsPreview(), contentId))
.WhereNotNull()
.Where(IsAllowedContentType)
.ToArray();
}
- private IPublishedContentCache? GetContentCache() =>
- _publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot)
- ? publishedSnapshot?.Content
- : null;
-
private IPublishedContent? ContentOrNullIfDisallowed(IPublishedContent? content)
=> content != null && IsAllowedContentType(content)
? content
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
index d27a7e676c..d6f7b480aa 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
@@ -36,7 +36,7 @@ public static partial class UmbracoBuilderExtensions
// devs can then modify this list on application startup
builder.ContentFinders()
.Append()
- .Append()
+ .Append()
.Append()
.Append()
/*.Append() // disabled, this is an odd finder */
@@ -47,7 +47,7 @@ public static partial class UmbracoBuilderExtensions
builder.HealthCheckNotificationMethods().Add(() => builder.TypeLoader.GetTypes());
builder.UrlProviders()
.Append()
- .Append();
+ .Append();
builder.MediaUrlProviders()
.Append();
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
index c39f05cc5e..829dbd7ae8 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
@@ -264,9 +264,6 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddSingleton();
- // register a basic/noop published snapshot service to be replaced
- Services.AddSingleton();
-
// Register ValueEditorCache used for validation
Services.AddSingleton();
@@ -412,6 +409,10 @@ namespace Umbraco.Cms.Core.DependencyInjection
// add validation services
Services.AddUnique();
+
+ // Routing
+ Services.AddUnique();
+ Services.AddHostedService();
}
}
}
diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/ListDescendantsFromCurrentPage.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/ListDescendantsFromCurrentPage.cshtml
index 455236ac03..f174b38495 100644
--- a/src/Umbraco.Core/EmbeddedResources/Snippets/ListDescendantsFromCurrentPage.cshtml
+++ b/src/Umbraco.Core/EmbeddedResources/Snippets/ListDescendantsFromCurrentPage.cshtml
@@ -1,16 +1,21 @@
@using Umbraco.Cms.Core
@using Umbraco.Cms.Core.Models.PublishedContent
+@using Umbraco.Cms.Core.PublishedCache
@using Umbraco.Cms.Core.Routing
+@using Umbraco.Cms.Core.Services.Navigation
@using Umbraco.Extensions
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@inject IPublishedValueFallback PublishedValueFallback
@inject IPublishedUrlProvider PublishedUrlProvider
+@inject IVariationContextAccessor VariationContextAccessor
+@inject IPublishedContentCache PublishedContentCache
+@inject IDocumentNavigationQueryService DocumentNavigationQueryService
@*
This snippet creates links for every single page (no matter how deep) below
the page currently being viewed by the website visitor, displayed as nested unordered HTML lists.
*@
-@{ var selection = Model?.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); }
+@{ var selection = Model?.Content.Children(VariationContextAccessor, PublishedContentCache, DocumentNavigationQueryService).Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); }
@* Ensure that the Current Page has children *@
@if (selection?.Length > 0)
@@ -28,7 +33,11 @@
@* if this child page has any children, where the property umbracoNaviHide is not True *@
@{
- var children = item.Children.Where(x => x.IsVisible(PublishedValueFallback)).ToArray();
+ var children = item
+ .Children(VariationContextAccessor, PublishedContentCache, DocumentNavigationQueryService)
+ .Where(x => x.IsVisible(PublishedValueFallback))
+ .ToArray();
+
if (children.Length > 0)
{
@* Call a local method to display the children *@
@@ -58,7 +67,11 @@
@* if the page has any children, where the property umbracoNaviHide is not True *@
@{
- var children = item.Children.Where(x => x.IsVisible(PublishedValueFallback)).ToArray();
+ var children = item
+ .Children(VariationContextAccessor, PublishedContentCache, DocumentNavigationQueryService)
+ .Where(x => x.IsVisible(PublishedValueFallback))
+ .ToArray();
+
if (children.Length > 0)
{
@* Recurse and call the ChildPages method to display the children *@
diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/SiteMap.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/SiteMap.cshtml
index 3a257cc161..20b31b6dcb 100644
--- a/src/Umbraco.Core/EmbeddedResources/Snippets/SiteMap.cshtml
+++ b/src/Umbraco.Core/EmbeddedResources/Snippets/SiteMap.cshtml
@@ -1,10 +1,15 @@
@using Umbraco.Cms.Core
@using Umbraco.Cms.Core.Models.PublishedContent
+@using Umbraco.Cms.Core.PublishedCache
@using Umbraco.Cms.Core.Routing
+@using Umbraco.Cms.Core.Services.Navigation
@using Umbraco.Extensions
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@inject IPublishedValueFallback PublishedValueFallback
@inject IPublishedUrlProvider PublishedUrlProvider
+@inject IVariationContextAccessor VariationContextAccessor
+@inject IPublishedContentCache PublishedContentCache
+@inject IDocumentNavigationQueryService DocumentNavigationQueryService
@*
This snippet makes a list of links of all visible pages of the site, as nested unordered HTML lists.
@@ -27,7 +32,10 @@
const int maxLevelForSitemap = 4;
@* Select visible children *@
- var selection = node?.Children.Where(x => x.IsVisible(PublishedValueFallback) && x.Level <= maxLevelForSitemap).ToArray();
+ var selection = node?
+ .Children(VariationContextAccessor, PublishedContentCache, DocumentNavigationQueryService)
+ .Where(x => x.IsVisible(PublishedValueFallback) && x.Level <= maxLevelForSitemap)
+ .ToArray();
@* If any items are returned, render a list *@
if (selection?.Length > 0)
diff --git a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
index ff4fd499f9..5139e23af9 100644
--- a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
+++ b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
@@ -9,6 +9,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Services.Navigation;
namespace Umbraco.Extensions;
@@ -119,16 +120,36 @@ public static class PublishedContentExtensions
///
/// The content type.
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The parent of content, of the given content type, else null.
- public static T? Parent(this IPublishedContent content)
+ public static T? Parent(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService)
where T : class, IPublishedContent
{
- if (content == null)
+ ArgumentNullException.ThrowIfNull(content);
+
+ return content.GetParent(publishedCache, navigationQueryService) as T;
+ }
+
+ private static IPublishedContent? GetParent(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService)
+ {
+ IPublishedContent? parent;
+ if (navigationQueryService.TryGetParentKey(content.Key, out Guid? parentKey))
{
- throw new ArgumentNullException(nameof(content));
+ parent = parentKey.HasValue ? publishedCache.GetById(parentKey.Value) : null;
+ }
+ else
+ {
+ throw new KeyNotFoundException($"Content with key '{content.Key}' was not found in the in-memory navigation structure.");
}
- return content.Parent as T;
+ return parent;
}
#endregion
@@ -497,41 +518,63 @@ public static class PublishedContentExtensions
/// Gets the ancestors of the content.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The ancestors of the content, in down-top order.
/// Does not consider the content itself.
- public static IEnumerable Ancestors(this IPublishedContent content) =>
- content.AncestorsOrSelf(false, null);
+ public static IEnumerable Ancestors(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService) =>
+ content.AncestorsOrSelf(publishedCache, navigationQueryService, false, null);
///
/// Gets the ancestors of the content, at a level lesser or equal to a specified level.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The level.
/// The ancestors of the content, at a level lesser or equal to the specified level, in down-top order.
/// Does not consider the content itself. Only content that are "high enough" in the tree are returned.
- public static IEnumerable Ancestors(this IPublishedContent content, int maxLevel) =>
- content.AncestorsOrSelf(false, n => n.Level <= maxLevel);
+ public static IEnumerable Ancestors(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int maxLevel) =>
+ content.AncestorsOrSelf(publishedCache, navigationQueryService, false, n => n.Level <= maxLevel);
///
/// Gets the ancestors of the content, of a specified content type.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The content type.
/// The ancestors of the content, of the specified content type, in down-top order.
/// Does not consider the content itself. Returns all ancestors, of the specified content type.
- public static IEnumerable Ancestors(this IPublishedContent content, string contentTypeAlias) =>
- content.AncestorsOrSelf(false, n => n.ContentType.Alias.InvariantEquals(contentTypeAlias));
+ public static IEnumerable Ancestors(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string contentTypeAlias) =>
+ content.AncestorsOrSelf(publishedCache, navigationQueryService, false, n => n.ContentType.Alias.InvariantEquals(contentTypeAlias));
///
/// Gets the ancestors of the content, of a specified content type.
///
/// The content type.
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The ancestors of the content, of the specified content type, in down-top order.
/// Does not consider the content itself. Returns all ancestors, of the specified content type.
- public static IEnumerable Ancestors(this IPublishedContent content)
+ public static IEnumerable Ancestors(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService)
where T : class, IPublishedContent =>
- content.Ancestors().OfType();
+ content.Ancestors(publishedCache, navigationQueryService).OfType();
///
/// Gets the ancestors of the content, at a level lesser or equal to a specified level, and of a specified content
@@ -539,6 +582,8 @@ public static class PublishedContentExtensions
///
/// The content type.
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The level.
///
/// The ancestors of the content, at a level lesser or equal to the specified level, and of the specified
@@ -548,22 +593,33 @@ public static class PublishedContentExtensions
/// Does not consider the content itself. Only content that are "high enough" in the trees, and of the
/// specified content type, are returned.
///
- public static IEnumerable Ancestors(this IPublishedContent content, int maxLevel)
+ public static IEnumerable Ancestors(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int maxLevel)
where T : class, IPublishedContent =>
- content.Ancestors(maxLevel).OfType();
+ content.Ancestors(publishedCache, navigationQueryService, maxLevel).OfType();
///
/// Gets the content and its ancestors.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The content and its ancestors, in down-top order.
- public static IEnumerable AncestorsOrSelf(this IPublishedContent content) =>
- content.AncestorsOrSelf(true, null);
+ public static IEnumerable AncestorsOrSelf(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService) =>
+ content.AncestorsOrSelf(publishedCache, navigationQueryService, true, null);
///
/// Gets the content and its ancestors, at a level lesser or equal to a specified level.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The level.
///
/// The content and its ancestors, at a level lesser or equal to the specified level,
@@ -573,30 +629,44 @@ public static class PublishedContentExtensions
/// Only content that are "high enough" in the tree are returned. So it may or may not begin
/// with the content itself, depending on its level.
///
- public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int maxLevel) =>
- content.AncestorsOrSelf(true, n => n.Level <= maxLevel);
+ public static IEnumerable AncestorsOrSelf(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int maxLevel) =>
+ content.AncestorsOrSelf(publishedCache, navigationQueryService, true, n => n.Level <= maxLevel);
///
/// Gets the content and its ancestors, of a specified content type.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The content type.
/// The content and its ancestors, of the specified content type, in down-top order.
/// May or may not begin with the content itself, depending on its content type.
- public static IEnumerable
- AncestorsOrSelf(this IPublishedContent content, string contentTypeAlias) =>
- content.AncestorsOrSelf(true, n => n.ContentType.Alias.InvariantEquals(contentTypeAlias));
+ public static IEnumerable AncestorsOrSelf(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string contentTypeAlias) =>
+ content.AncestorsOrSelf(publishedCache, navigationQueryService, true, n => n.ContentType.Alias.InvariantEquals(contentTypeAlias));
///
/// Gets the content and its ancestors, of a specified content type.
///
/// The content type.
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The content and its ancestors, of the specified content type, in down-top order.
/// May or may not begin with the content itself, depending on its content type.
- public static IEnumerable AncestorsOrSelf(this IPublishedContent content)
+ public static IEnumerable AncestorsOrSelf(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService)
where T : class, IPublishedContent =>
- content.AncestorsOrSelf().OfType();
+ content.AncestorsOrSelf(publishedCache, navigationQueryService).OfType();
///
/// Gets the content and its ancestor, at a lever lesser or equal to a specified level, and of a specified content
@@ -604,69 +674,104 @@ public static class PublishedContentExtensions
///
/// The content type.
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The level.
///
/// The content and its ancestors, at a level lesser or equal to the specified level, and of the specified
/// content type, in down-top order.
///
/// May or may not begin with the content itself, depending on its level and content type.
- public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int maxLevel)
+ public static IEnumerable AncestorsOrSelf(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int maxLevel)
where T : class, IPublishedContent =>
- content.AncestorsOrSelf(maxLevel).OfType();
+ content.AncestorsOrSelf(publishedCache, navigationQueryService, maxLevel).OfType();
///
/// Gets the ancestor of the content, ie its parent.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The ancestor of the content.
/// This method is here for consistency purposes but does not make much sense.
- public static IPublishedContent? Ancestor(this IPublishedContent content) => content.Parent;
+ public static IPublishedContent? Ancestor(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService)
+ => content.GetParent(publishedCache, navigationQueryService);
///
/// Gets the nearest ancestor of the content, at a lever lesser or equal to a specified level.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The level.
/// The nearest (in down-top order) ancestor of the content, at a level lesser or equal to the specified level.
/// Does not consider the content itself. May return null.
- public static IPublishedContent? Ancestor(this IPublishedContent content, int maxLevel) =>
- content.EnumerateAncestors(false).FirstOrDefault(x => x.Level <= maxLevel);
+ public static IPublishedContent? Ancestor(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int maxLevel) =>
+ content.EnumerateAncestors(publishedCache, navigationQueryService, false).FirstOrDefault(x => x.Level <= maxLevel);
///
/// Gets the nearest ancestor of the content, of a specified content type.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The content type alias.
/// The nearest (in down-top order) ancestor of the content, of the specified content type.
/// Does not consider the content itself. May return null.
- public static IPublishedContent? Ancestor(this IPublishedContent content, string contentTypeAlias) => content
- .EnumerateAncestors(false).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias));
+ public static IPublishedContent? Ancestor(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string contentTypeAlias) =>
+ content.EnumerateAncestors(publishedCache, navigationQueryService, false).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias));
///
/// Gets the nearest ancestor of the content, of a specified content type.
///
/// The content type.
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The nearest (in down-top order) ancestor of the content, of the specified content type.
/// Does not consider the content itself. May return null.
- public static T? Ancestor(this IPublishedContent content)
+ public static T? Ancestor(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService)
where T : class, IPublishedContent =>
- content.Ancestors().FirstOrDefault();
+ content.Ancestors(publishedCache, navigationQueryService).FirstOrDefault();
///
/// Gets the nearest ancestor of the content, at the specified level and of the specified content type.
///
/// The content type.
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The level.
/// The ancestor of the content, at the specified level and of the specified content type.
///
/// Does not consider the content itself. If the ancestor at the specified level is
/// not of the specified type, returns null.
///
- public static T? Ancestor(this IPublishedContent content, int maxLevel)
+ public static T? Ancestor(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int maxLevel)
where T : class, IPublishedContent =>
- content.Ancestors(maxLevel).FirstOrDefault();
+ content.Ancestors(publishedCache, navigationQueryService, maxLevel).FirstOrDefault();
///
/// Gets the content or its nearest ancestor.
@@ -680,32 +785,49 @@ public static class PublishedContentExtensions
/// Gets the content or its nearest ancestor, at a lever lesser or equal to a specified level.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The level.
/// The content or its nearest (in down-top order) ancestor, at a level lesser or equal to the specified level.
/// May or may not return the content itself depending on its level. May return null.
- public static IPublishedContent AncestorOrSelf(this IPublishedContent content, int maxLevel) =>
- content.EnumerateAncestors(true).FirstOrDefault(x => x.Level <= maxLevel) ?? content;
+ public static IPublishedContent AncestorOrSelf(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int maxLevel) =>
+ content.EnumerateAncestors(publishedCache, navigationQueryService, true).FirstOrDefault(x => x.Level <= maxLevel) ?? content;
///
/// Gets the content or its nearest ancestor, of a specified content type.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The content type.
/// The content or its nearest (in down-top order) ancestor, of the specified content type.
/// May or may not return the content itself depending on its content type. May return null.
- public static IPublishedContent AncestorOrSelf(this IPublishedContent content, string contentTypeAlias) => content
- .EnumerateAncestors(true).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)) ?? content;
+ public static IPublishedContent AncestorOrSelf(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string contentTypeAlias) => content
+ .EnumerateAncestors(publishedCache, navigationQueryService, true).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)) ?? content;
///
/// Gets the content or its nearest ancestor, of a specified content type.
///
/// The content type.
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The content or its nearest (in down-top order) ancestor, of the specified content type.
/// May or may not return the content itself depending on its content type. May return null.
- public static T? AncestorOrSelf(this IPublishedContent content)
+ public static T? AncestorOrSelf(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService)
where T : class, IPublishedContent =>
- content.AncestorsOrSelf().FirstOrDefault();
+ content.AncestorsOrSelf(publishedCache, navigationQueryService).FirstOrDefault();
///
/// Gets the content or its nearest ancestor, at a lever lesser or equal to a specified level, and of a specified
@@ -713,15 +835,26 @@ public static class PublishedContentExtensions
///
/// The content type.
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The level.
///
- public static T? AncestorOrSelf(this IPublishedContent content, int maxLevel)
+ public static T? AncestorOrSelf(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int maxLevel)
where T : class, IPublishedContent =>
- content.AncestorsOrSelf(maxLevel).FirstOrDefault();
+ content.AncestorsOrSelf(publishedCache, navigationQueryService, maxLevel).FirstOrDefault();
- public static IEnumerable AncestorsOrSelf(this IPublishedContent content, bool orSelf, Func? func)
+ public static IEnumerable AncestorsOrSelf(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ bool orSelf,
+ Func? func)
{
- IEnumerable ancestorsOrSelf = content.EnumerateAncestors(orSelf);
+ IEnumerable ancestorsOrSelf = content.EnumerateAncestors(publishedCache, navigationQueryService, orSelf);
return func == null ? ancestorsOrSelf : ancestorsOrSelf.Where(func);
}
@@ -729,9 +862,15 @@ public static class PublishedContentExtensions
/// Enumerates ancestors of the content, bottom-up.
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// Indicates whether the content should be included.
/// Enumerates bottom-up ie walking up the tree (parent, grand-parent, etc).
- internal static IEnumerable EnumerateAncestors(this IPublishedContent? content, bool orSelf)
+ internal static IEnumerable EnumerateAncestors(
+ this IPublishedContent? content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ bool orSelf)
{
if (content == null)
{
@@ -743,7 +882,7 @@ public static class PublishedContentExtensions
yield return content;
}
- while ((content = content.Parent) != null)
+ while ((content = content.GetParent(publishedCache, navigationQueryService)) != null)
{
yield return content;
}
@@ -757,18 +896,26 @@ public static class PublishedContentExtensions
/// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified .
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// Indicates whether the specified content should be included.
///
/// The breadcrumbs (ancestors and self, top to bottom) for the specified .
///
- public static IEnumerable Breadcrumbs(this IPublishedContent content, bool andSelf = true) =>
- content.AncestorsOrSelf(andSelf, null).Reverse();
+ public static IEnumerable Breadcrumbs(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ bool andSelf = true) =>
+ content.AncestorsOrSelf(publishedCache, navigationQueryService, andSelf, null).Reverse();
///
/// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified at a level
/// higher or equal to .
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// The minimum level.
/// Indicates whether the specified content should be included.
///
@@ -777,9 +924,11 @@ public static class PublishedContentExtensions
///
public static IEnumerable Breadcrumbs(
this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
int minLevel,
bool andSelf = true) =>
- content.AncestorsOrSelf(andSelf, n => n.Level >= minLevel).Reverse();
+ content.AncestorsOrSelf(publishedCache, navigationQueryService, andSelf, n => n.Level >= minLevel).Reverse();
///
/// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified at a level
@@ -787,12 +936,18 @@ public static class PublishedContentExtensions
///
/// The root content type.
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
/// Indicates whether the specified content should be included.
///
/// The breadcrumbs (ancestors and self, top to bottom) for the specified at a level higher
/// or equal to the specified root content type .
///
- public static IEnumerable Breadcrumbs(this IPublishedContent content, bool andSelf = true)
+ public static IEnumerable Breadcrumbs(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ bool andSelf = true)
where T : class, IPublishedContent
{
static IEnumerable TakeUntil(IEnumerable source, Func predicate)
@@ -807,7 +962,7 @@ public static class PublishedContentExtensions
}
}
- return TakeUntil(content.AncestorsOrSelf(andSelf, null), n => n is T).Reverse();
+ return TakeUntil(content.AncestorsOrSelf(publishedCache, navigationQueryService, andSelf, null), n => n is T).Reverse();
}
#endregion
@@ -819,18 +974,25 @@ public static class PublishedContentExtensions
///
///
/// Variation context accessor.
+ ///
///
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
+ ///
///
///
/// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot
///
public static IEnumerable DescendantsOrSelfOfType(
- this IEnumerable parentNodes, IVariationContextAccessor variationContextAccessor, string docTypeAlias, string? culture = null) => parentNodes.SelectMany(x =>
- x.DescendantsOrSelfOfType(variationContextAccessor, docTypeAlias, culture));
+ this IEnumerable parentNodes,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string docTypeAlias,
+ string? culture = null) => parentNodes.SelectMany(x =>
+ x.DescendantsOrSelfOfType(variationContextAccessor, publishedCache, navigationQueryService, docTypeAlias, culture));
///
/// Returns all DescendantsOrSelf of all content referenced
@@ -845,9 +1007,14 @@ public static class PublishedContentExtensions
///
/// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot
///
- public static IEnumerable DescendantsOrSelf(this IEnumerable parentNodes, IVariationContextAccessor variationContextAccessor, string? culture = null)
+ public static IEnumerable DescendantsOrSelf(
+ this IEnumerable parentNodes,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null)
where T : class, IPublishedContent =>
- parentNodes.SelectMany(x => x.DescendantsOrSelf(variationContextAccessor, culture));
+ parentNodes.SelectMany(x => x.DescendantsOrSelf(variationContextAccessor, publishedCache, navigationQueryService, culture));
// as per XPath 1.0 specs �2.2,
// - the descendant axis contains the descendants of the context node; a descendant is a child or a child of a child and so on; thus
@@ -867,85 +1034,199 @@ public static class PublishedContentExtensions
// - every node occurs before all of its children and descendants.
// - the relative order of siblings is the order in which they occur in the children property of their parent node.
// - children and descendants occur before following siblings.
- public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) =>
- content.DescendantsOrSelf(variationContextAccessor, false, null, culture);
+ public static IEnumerable Descendants(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null) =>
+ content.DescendantsOrSelf(variationContextAccessor, publishedCache, navigationQueryService, false, null, culture);
- public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) =>
- content.DescendantsOrSelf(variationContextAccessor, false, p => p.Level >= level, culture);
+ public static IEnumerable Descendants(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int level,
+ string? culture = null) =>
+ content.DescendantsOrSelf(variationContextAccessor, publishedCache, navigationQueryService, false, p => p.Level >= level, culture);
- public static IEnumerable DescendantsOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) =>
- content.DescendantsOrSelf(variationContextAccessor, false, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture);
+ public static IEnumerable DescendantsOfType(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string contentTypeAlias, string? culture = null) =>
+ content.DescendantsOrSelf(variationContextAccessor, publishedCache, navigationQueryService, false, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture);
- public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
+ public static IEnumerable Descendants(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null)
where T : class, IPublishedContent =>
- content.Descendants(variationContextAccessor, culture).OfType();
+ content.Descendants(variationContextAccessor, publishedCache, navigationQueryService, culture).OfType();
- public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null)
+ public static IEnumerable Descendants(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int level,
+ string? culture = null)
where T : class, IPublishedContent =>
- content.Descendants(variationContextAccessor, level, culture).OfType();
+ content.Descendants(variationContextAccessor, publishedCache, navigationQueryService, level, culture).OfType();
- public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) =>
- content.DescendantsOrSelf(variationContextAccessor, true, null, culture);
+ public static IEnumerable DescendantsOrSelf(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null) =>
+ content.DescendantsOrSelf(variationContextAccessor, publishedCache, navigationQueryService, true, null, culture);
- public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) =>
- content.DescendantsOrSelf(variationContextAccessor, true, p => p.Level >= level, culture);
+ public static IEnumerable DescendantsOrSelf(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int level,
+ string? culture = null) =>
+ content.DescendantsOrSelf(variationContextAccessor, publishedCache, navigationQueryService, true, p => p.Level >= level, culture);
- public static IEnumerable DescendantsOrSelfOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) =>
- content.DescendantsOrSelf(variationContextAccessor, true, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture);
+ public static IEnumerable DescendantsOrSelfOfType(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string contentTypeAlias,
+ string? culture = null) =>
+ content.DescendantsOrSelf(variationContextAccessor, publishedCache, navigationQueryService, true, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture);
- public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
+ public static IEnumerable DescendantsOrSelf(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null)
where T : class, IPublishedContent =>
- content.DescendantsOrSelf(variationContextAccessor, culture).OfType();
+ content.DescendantsOrSelf(variationContextAccessor, publishedCache, navigationQueryService, culture).OfType();
- public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null)
+ public static IEnumerable DescendantsOrSelf(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int level,
+ string? culture = null)
where T : class, IPublishedContent =>
- content.DescendantsOrSelf(variationContextAccessor, level, culture).OfType();
+ content.DescendantsOrSelf(variationContextAccessor, publishedCache, navigationQueryService, level, culture).OfType();
- public static IPublishedContent? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) =>
- content.Children(variationContextAccessor, culture)?.FirstOrDefault();
+ public static IPublishedContent? Descendant(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null) =>
+ content.Children(variationContextAccessor, publishedCache, navigationQueryService, culture)?.FirstOrDefault();
- public static IPublishedContent? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content
- .EnumerateDescendants(variationContextAccessor, false, culture).FirstOrDefault(x => x.Level == level);
+ public static IPublishedContent? Descendant(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int level,
+ string? culture = null) => content
+ .EnumerateDescendants(variationContextAccessor, publishedCache, navigationQueryService, false, culture).FirstOrDefault(x => x.Level == level);
- public static IPublishedContent? DescendantOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => content
- .EnumerateDescendants(variationContextAccessor, false, culture)
+ public static IPublishedContent? DescendantOfType(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string contentTypeAlias,
+ string? culture = null) => content
+ .EnumerateDescendants(variationContextAccessor, publishedCache, navigationQueryService, false, culture)
.FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias));
- public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
+ public static T? Descendant(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null)
where T : class, IPublishedContent =>
- content.EnumerateDescendants(variationContextAccessor, false, culture).FirstOrDefault(x => x is T) as T;
+ content.EnumerateDescendants(variationContextAccessor, publishedCache, navigationQueryService, false, culture).FirstOrDefault(x => x is T) as T;
- public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null)
+ public static T? Descendant(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int level,
+ string? culture = null)
where T : class, IPublishedContent =>
- content.Descendant(variationContextAccessor, level, culture) as T;
+ content.Descendant(variationContextAccessor, publishedCache, navigationQueryService, level, culture) as T;
public static IPublishedContent DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) => content;
- public static IPublishedContent? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content
- .EnumerateDescendants(variationContextAccessor, true, culture).FirstOrDefault(x => x.Level == level);
+ public static IPublishedContent? DescendantOrSelf(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int level,
+ string? culture = null) => content
+ .EnumerateDescendants(variationContextAccessor, publishedCache, navigationQueryService, true, culture).FirstOrDefault(x => x.Level == level);
- public static IPublishedContent? DescendantOrSelfOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => content
- .EnumerateDescendants(variationContextAccessor, true, culture)
+ public static IPublishedContent? DescendantOrSelfOfType(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string contentTypeAlias,
+ string? culture = null) => content
+ .EnumerateDescendants(variationContextAccessor, publishedCache, navigationQueryService, true, culture)
.FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias));
- public static T? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
+ public static T? DescendantOrSelf(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null)
where T : class, IPublishedContent =>
- content.EnumerateDescendants(variationContextAccessor, true, culture).FirstOrDefault(x => x is T) as T;
+ content.EnumerateDescendants(variationContextAccessor, publishedCache, navigationQueryService, true, culture).FirstOrDefault(x => x is T) as T;
- public static T? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null)
+ public static T? DescendantOrSelf(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ int level,
+ string? culture = null)
where T : class, IPublishedContent =>
- content.DescendantOrSelf(variationContextAccessor, level, culture) as T;
+ content.DescendantOrSelf(variationContextAccessor, publishedCache, navigationQueryService, level, culture) as T;
internal static IEnumerable DescendantsOrSelf(
this IPublishedContent content,
IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
bool orSelf,
Func? func,
string? culture = null) =>
- content.EnumerateDescendants(variationContextAccessor, orSelf, culture)
+ content.EnumerateDescendants(variationContextAccessor, publishedCache, navigationQueryService, orSelf, culture)
.Where(x => func == null || func(x));
- internal static IEnumerable EnumerateDescendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, bool orSelf, string? culture = null)
+ internal static IEnumerable EnumerateDescendants(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ bool orSelf,
+ string? culture = null)
{
if (content == null)
{
@@ -957,25 +1238,30 @@ public static class PublishedContentExtensions
yield return content;
}
- IEnumerable? children = content.Children(variationContextAccessor, culture);
+ IEnumerable? children = content.Children(variationContextAccessor, publishedCache, navigationQueryService, culture);
if (children is not null)
{
foreach (IPublishedContent desc in children.SelectMany(x =>
- x.EnumerateDescendants(variationContextAccessor, culture)))
+ x.EnumerateDescendants(variationContextAccessor, publishedCache, navigationQueryService, culture)))
{
yield return desc;
}
}
}
- internal static IEnumerable EnumerateDescendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
+ internal static IEnumerable EnumerateDescendants(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null)
{
yield return content;
- IEnumerable? children = content.Children(variationContextAccessor, culture);
+ IEnumerable? children = content.Children(variationContextAccessor, publishedCache, navigationQueryService, culture);
if (children is not null)
{
foreach (IPublishedContent desc in children.SelectMany(x =>
- x.EnumerateDescendants(variationContextAccessor, culture)))
+ x.EnumerateDescendants(variationContextAccessor, publishedCache, navigationQueryService, culture)))
{
yield return desc;
}
@@ -991,10 +1277,12 @@ public static class PublishedContentExtensions
///
/// The content item.
///
+ ///
///
/// The specific culture to get the URL children for. Default is null which will use the current culture in
///
///
+ ///
///
/// Gets children that are available for the specified culture.
/// Children are sorted by their sortOrder.
@@ -1012,18 +1300,32 @@ public static class PublishedContentExtensions
/// However, if an empty string is specified only invariant children are returned.
///
///
- public static IEnumerable Children(this IPublishedContent content, IVariationContextAccessor? variationContextAccessor, string? culture = null)
+ public static IEnumerable Children(
+ this IPublishedContent content,
+ IVariationContextAccessor? variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null)
{
// handle context culture for variant
- if (culture == null)
+ if (culture is null)
{
culture = variationContextAccessor?.VariationContext?.Culture ?? string.Empty;
}
- IEnumerable? children = content.ChildrenForAllCultures;
- return (culture == "*"
- ? children : children?.Where(x => x.IsInvariantOrHasCulture(culture)))
- ?? Enumerable.Empty();
+ if (navigationQueryService.TryGetChildrenKeys(content.Key, out IEnumerable childrenKeys) is false)
+ {
+ return [];
+ }
+
+ IEnumerable children = childrenKeys.Select(publishedCache.GetById).WhereNotNull();
+
+ if (culture == "*")
+ {
+ return children;
+ }
+
+ return children.Where(x => x.IsInvariantOrHasCulture(culture)) ?? [];
}
///
@@ -1031,11 +1333,13 @@ public static class PublishedContentExtensions
///
/// The content.
/// The accessor for VariationContext
+ ///
/// The predicate.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
+ ///
/// The children of the content, filtered by the predicate.
///
/// Children are sorted by their sortOrder.
@@ -1043,23 +1347,34 @@ public static class PublishedContentExtensions
public static IEnumerable Children(
this IPublishedContent content,
IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
Func predicate,
string? culture = null) =>
- content.Children(variationContextAccessor, culture).Where(predicate);
+ content.Children(variationContextAccessor, publishedCache, navigationQueryService, culture).Where(predicate);
///
/// Gets the children of the content, of any of the specified types.
///
/// The content.
+ ///
/// The accessor for the VariationContext
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The content type alias.
+ ///
/// The children of the content, of any of the specified types.
- public static IEnumerable ChildrenOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? contentTypeAlias, string? culture = null) =>
- content.Children(variationContextAccessor, x => x.ContentType.Alias.InvariantEquals(contentTypeAlias), culture);
+ public static IEnumerable ChildrenOfType(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? contentTypeAlias,
+ string? culture = null) =>
+ content.Children(variationContextAccessor, publishedCache, navigationQueryService, x => x.ContentType.Alias.InvariantEquals(contentTypeAlias),
+ culture);
///
/// Gets the children of the content, of a given content type.
@@ -1075,31 +1390,71 @@ public static class PublishedContentExtensions
///
/// Children are sorted by their sortOrder.
///
- public static IEnumerable Children(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
+ public static IEnumerable Children(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null)
where T : class, IPublishedContent =>
- content.Children(variationContextAccessor, culture).OfType();
+ content.Children(variationContextAccessor, publishedCache, navigationQueryService, culture).OfType();
- public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) =>
- content.Children(variationContextAccessor, culture)?.FirstOrDefault();
+ public static IPublishedContent? FirstChild(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null) =>
+ content.Children(variationContextAccessor, publishedCache, navigationQueryService, culture)?.FirstOrDefault();
///
/// Gets the first child of the content, of a given content type.
///
- public static IPublishedContent? FirstChildOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) =>
- content.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture)?.FirstOrDefault();
+ public static IPublishedContent? FirstChildOfType(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string contentTypeAlias,
+ string? culture = null) =>
+ content.ChildrenOfType(variationContextAccessor, publishedCache, navigationQueryService, contentTypeAlias, culture)?.FirstOrDefault();
- public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Func predicate, string? culture = null) => content.Children(variationContextAccessor, predicate, culture)?.FirstOrDefault();
+ public static IPublishedContent? FirstChild(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ Func predicate,
+ string? culture = null)
+ => content.Children(variationContextAccessor, publishedCache, navigationQueryService, predicate, culture)?.FirstOrDefault();
- public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Guid uniqueId, string? culture = null) => content
- .Children(variationContextAccessor, x => x.Key == uniqueId, culture)?.FirstOrDefault();
+ public static IPublishedContent? FirstChild(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ Guid uniqueId,
+ string? culture = null) => content
+ .Children(variationContextAccessor, publishedCache, navigationQueryService, x => x.Key == uniqueId, culture)?.FirstOrDefault();
- public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
+ public static T? FirstChild(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null)
where T : class, IPublishedContent =>
- content.Children(variationContextAccessor, culture)?.FirstOrDefault();
+ content.Children(variationContextAccessor, publishedCache, navigationQueryService, culture)?.FirstOrDefault();
- public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Func predicate, string? culture = null)
+ public static T? FirstChild(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ Func predicate,
+ string? culture = null)
where T : class, IPublishedContent =>
- content.Children(variationContextAccessor, culture)?.FirstOrDefault(predicate);
+ content.Children(variationContextAccessor, publishedCache, navigationQueryService, culture)?.FirstOrDefault(predicate);
#endregion
@@ -1109,22 +1464,24 @@ public static class PublishedContentExtensions
/// Gets the siblings of the content.
///
/// The content.
- /// Published snapshot instance
+ /// The navigation service
/// Variation context accessor.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
+ /// The content cache instance.
/// The siblings of the content.
///
/// Note that in V7 this method also return the content node self.
///
public static IEnumerable Siblings(
this IPublishedContent content,
- IPublishedSnapshot? publishedSnapshot,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
IVariationContextAccessor variationContextAccessor,
string? culture = null) =>
- SiblingsAndSelf(content, publishedSnapshot, variationContextAccessor, culture)
+ SiblingsAndSelf(content, publishedCache, navigationQueryService, variationContextAccessor, culture)
?.Where(x => x.Id != content.Id) ?? Enumerable.Empty();
///
@@ -1144,11 +1501,12 @@ public static class PublishedContentExtensions
///
public static IEnumerable SiblingsOfType(
this IPublishedContent content,
- IPublishedSnapshot? publishedSnapshot,
IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
string contentTypeAlias,
string? culture = null) =>
- SiblingsAndSelfOfType(content, publishedSnapshot, variationContextAccessor, contentTypeAlias, culture)
+ SiblingsAndSelfOfType(content, variationContextAccessor, publishedCache, navigationQueryService, contentTypeAlias, culture)
?.Where(x => x.Id != content.Id) ?? Enumerable.Empty();
///
@@ -1166,16 +1524,22 @@ public static class PublishedContentExtensions
///
/// Note that in V7 this method also return the content node self.
///
- public static IEnumerable Siblings(this IPublishedContent content, IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, string? culture = null)
+ public static IEnumerable Siblings(
+ this IPublishedContent content,
+ IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
+ string? culture = null)
where T : class, IPublishedContent =>
- SiblingsAndSelf(content, publishedSnapshot, variationContextAccessor, culture)
+ SiblingsAndSelf(content, variationContextAccessor, publishedCache, navigationQueryService, culture)
?.Where(x => x.Id != content.Id) ?? Enumerable.Empty();
///
/// Gets the siblings of the content including the node itself to indicate the position.
///
/// The content.
- /// Published snapshot instance
+ /// Cache instance.
+ /// The navigation service.
/// Variation context accessor.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
@@ -1184,13 +1548,30 @@ public static class PublishedContentExtensions
/// The siblings of the content including the node itself.
public static IEnumerable? SiblingsAndSelf(
this IPublishedContent content,
- IPublishedSnapshot? publishedSnapshot,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
IVariationContextAccessor variationContextAccessor,
- string? culture = null) =>
- content.Parent != null
- ? content.Parent.Children(variationContextAccessor, culture)
- : publishedSnapshot?.Content?.GetAtRoot(culture)
+ string? culture = null)
+ {
+ var success = navigationQueryService.TryGetParentKey(content.Key, out Guid? parentKey);
+
+ if (success is false || parentKey is null)
+ {
+ if (navigationQueryService.TryGetRootKeys(out IEnumerable childrenKeys) is false)
+ {
+ return null;
+ }
+
+ return childrenKeys
+ .Select(publishedCache.GetById)
+ .WhereNotNull()
.WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
+ }
+
+ return navigationQueryService.TryGetChildrenKeys(parentKey.Value, out IEnumerable siblingKeys) is false
+ ? null
+ : siblingKeys.Select(publishedCache.GetById).WhereNotNull();
+ }
///
/// Gets the siblings of the content including the node itself to indicate the position, of a given content type.
@@ -1206,15 +1587,33 @@ public static class PublishedContentExtensions
/// The siblings of the content including the node itself, of the given content type.
public static IEnumerable SiblingsAndSelfOfType(
this IPublishedContent content,
- IPublishedSnapshot? publishedSnapshot,
IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
string contentTypeAlias,
- string? culture = null) =>
- (content.Parent != null
- ? content.Parent.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture)
- : publishedSnapshot?.Content?.GetAtRoot(culture).OfTypes(contentTypeAlias)
- .WhereIsInvariantOrHasCulture(variationContextAccessor, culture))
- ?? Enumerable.Empty();
+ string? culture = null)
+ {
+
+ var parentSuccess = navigationQueryService.TryGetParentKey(content.Key, out Guid? parentKey);
+
+ IPublishedContent? parent = parentKey is null ? null : publishedCache.GetById(parentKey.Value);
+
+ if (parentSuccess is false || parent is null)
+ {
+ if (navigationQueryService.TryGetRootKeys(out IEnumerable childrenKeys) is false)
+ {
+ return Enumerable.Empty();
+ }
+
+ return childrenKeys
+ .Select(publishedCache.GetById)
+ .WhereNotNull()
+ .OfTypes(contentTypeAlias)
+ .WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
+ }
+
+ return parent.ChildrenOfType(variationContextAccessor, publishedCache, navigationQueryService, contentTypeAlias, culture);
+ }
///
/// Gets the siblings of the content including the node itself to indicate the position, of a given content type.
@@ -1230,15 +1629,32 @@ public static class PublishedContentExtensions
/// The siblings of the content including the node itself, of the given content type.
public static IEnumerable SiblingsAndSelf(
this IPublishedContent content,
- IPublishedSnapshot? publishedSnapshot,
IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
string? culture = null)
- where T : class, IPublishedContent =>
- (content.Parent != null
- ? content.Parent.Children(variationContextAccessor, culture)
- : publishedSnapshot?.Content?.GetAtRoot(culture).OfType()
- .WhereIsInvariantOrHasCulture(variationContextAccessor, culture))
- ?? Enumerable.Empty();
+ where T : class, IPublishedContent
+ {
+ var parentSuccess = navigationQueryService.TryGetParentKey(content.Key, out Guid? parentKey);
+ IPublishedContent? parent = parentKey is null ? null : publishedCache.GetById(parentKey.Value);
+
+ if (parentSuccess is false || parent is null)
+ {
+ var rootSuccess = navigationQueryService.TryGetRootKeys(out IEnumerable rootKeys);
+ if (rootSuccess is false)
+ {
+ return [];
+ }
+
+ return rootKeys
+ .Select(publishedCache.GetById)
+ .WhereNotNull()
+ .WhereIsInvariantOrHasCulture(variationContextAccessor, culture)
+ .OfType();
+ }
+
+ return parent.Children(variationContextAccessor, publishedCache, navigationQueryService, culture);
+ }
#endregion
@@ -1248,6 +1664,8 @@ public static class PublishedContentExtensions
/// Gets the root content (ancestor or self at level 1) for the specified .
///
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
///
/// The root content (ancestor or self at level 1) for the specified .
///
@@ -1256,7 +1674,10 @@ public static class PublishedContentExtensions
/// with maxLevel
/// set to 1.
///
- public static IPublishedContent Root(this IPublishedContent content) => content.AncestorOrSelf(1);
+ public static IPublishedContent Root(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService) => content.AncestorOrSelf(publishedCache, navigationQueryService, 1);
///
/// Gets the root content (ancestor or self at level 1) for the specified if it's of the
@@ -1264,6 +1685,8 @@ public static class PublishedContentExtensions
///
/// The content type.
/// The content.
+ /// The content cache.
+ /// The query service for the in-memory navigation structure.
///
/// The root content (ancestor or self at level 1) for the specified of content type
/// .
@@ -1273,9 +1696,12 @@ public static class PublishedContentExtensions
/// with
/// maxLevel set to 1.
///
- public static T? Root(this IPublishedContent content)
+ public static T? Root(
+ this IPublishedContent content,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService)
where T : class, IPublishedContent =>
- content.AncestorOrSelf(1);
+ content.AncestorOrSelf(publishedCache, navigationQueryService, 1);
#endregion
@@ -1315,13 +1741,15 @@ public static class PublishedContentExtensions
public static DataTable ChildrenAsTable(
this IPublishedContent content,
IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
IContentTypeService contentTypeService,
IMediaTypeService mediaTypeService,
IMemberTypeService memberTypeService,
IPublishedUrlProvider publishedUrlProvider,
string contentTypeAliasFilter = "",
string? culture = null)
- => GenerateDataTable(content, variationContextAccessor, contentTypeService, mediaTypeService, memberTypeService, publishedUrlProvider, contentTypeAliasFilter, culture);
+ => GenerateDataTable(content, variationContextAccessor, publishedCache, navigationQueryService, contentTypeService, mediaTypeService, memberTypeService, publishedUrlProvider, contentTypeAliasFilter, culture);
///
/// Gets the children of the content in a DataTable.
@@ -1341,6 +1769,8 @@ public static class PublishedContentExtensions
private static DataTable GenerateDataTable(
IPublishedContent content,
IVariationContextAccessor variationContextAccessor,
+ IPublishedCache publishedCache,
+ INavigationQueryService navigationQueryService,
IContentTypeService contentTypeService,
IMediaTypeService mediaTypeService,
IMemberTypeService memberTypeService,
@@ -1349,10 +1779,10 @@ public static class PublishedContentExtensions
string? culture = null)
{
IPublishedContent? firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace()
- ? content.Children(variationContextAccessor, culture)?.Any() ?? false
- ? content.Children(variationContextAccessor, culture)?.ElementAt(0)
+ ? content.Children(variationContextAccessor, publishedCache, navigationQueryService, culture)?.Any() ?? false
+ ? content.Children(variationContextAccessor, publishedCache, navigationQueryService, culture)?.ElementAt(0)
: null
- : content.Children(variationContextAccessor, culture)
+ : content.Children(variationContextAccessor, publishedCache, navigationQueryService, culture)
?.FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAliasFilter));
if (firstNode == null)
{
@@ -1375,7 +1805,7 @@ public static class PublishedContentExtensions
List>, IEnumerable>>>
tableData = DataTableExtensions.CreateTableData();
IOrderedEnumerable? children =
- content.Children(variationContextAccessor)?.OrderBy(x => x.SortOrder);
+ content.Children(variationContextAccessor, publishedCache, navigationQueryService)?.OrderBy(x => x.SortOrder);
if (children is not null)
{
// loop through each child and create row data for it
diff --git a/src/Umbraco.Core/Extensions/PublishedSnapshotAccessorExtensions.cs b/src/Umbraco.Core/Extensions/PublishedSnapshotAccessorExtensions.cs
deleted file mode 100644
index 5e6d356674..0000000000
--- a/src/Umbraco.Core/Extensions/PublishedSnapshotAccessorExtensions.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Umbraco.Cms.Core.PublishedCache;
-
-namespace Umbraco.Extensions;
-
-public static class PublishedSnapshotAccessorExtensions
-{
- public static IPublishedSnapshot GetRequiredPublishedSnapshot(
- this IPublishedSnapshotAccessor publishedSnapshotAccessor)
- {
- if (publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot))
- {
- return publishedSnapshot!;
- }
-
- throw new InvalidOperationException("Wasn't possible to a get a valid Snapshot");
- }
-}
diff --git a/src/Umbraco.Core/Factories/NavigationFactory.cs b/src/Umbraco.Core/Factories/NavigationFactory.cs
deleted file mode 100644
index a95cbf68a5..0000000000
--- a/src/Umbraco.Core/Factories/NavigationFactory.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System.Collections.Concurrent;
-using Umbraco.Cms.Core.Models;
-using Umbraco.Cms.Core.Models.Navigation;
-
-namespace Umbraco.Cms.Core.Factories;
-
-internal static class NavigationFactory
-{
- ///
- /// Builds a dictionary of NavigationNode objects from a given dataset.
- ///
- /// A dictionary of objects with key corresponding to their unique Guid.
- /// The objects used to build the navigation nodes dictionary.
- public static void BuildNavigationDictionary(ConcurrentDictionary nodesStructure, IEnumerable entities)
- {
- var entityList = entities.ToList();
- Dictionary idToKeyMap = entityList.ToDictionary(x => x.Id, x => x.Key);
-
- foreach (INavigationModel entity in entityList)
- {
- var node = new NavigationNode(entity.Key);
- nodesStructure[entity.Key] = node;
-
- // We don't set the parent for items under root, it will stay null
- if (entity.ParentId == -1)
- {
- continue;
- }
-
- if (idToKeyMap.TryGetValue(entity.ParentId, out Guid parentKey) is false)
- {
- continue;
- }
-
- // If the parent node exists in the nodesStructure, add the node to the parent's children (parent is set as well)
- if (nodesStructure.TryGetValue(parentKey, out NavigationNode? parentNode))
- {
- parentNode.AddChild(node);
- }
- }
- }
-}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs
index e197f1d59e..5e263cf4c0 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs
@@ -62,13 +62,63 @@ public abstract class BlockEditorDataConverter
public BlockEditorData Convert(TValue? value)
{
- if (value?.GetLayouts() is not IEnumerable layouts)
+ if (value is not null)
+ {
+ var converted = ConvertOriginalBlockFormat(value.ContentData);
+ if (converted)
+ {
+ ConvertOriginalBlockFormat(value.SettingsData);
+ AmendExpose(value);
+ }
+ }
+
+ TLayout[]? layouts = value?.GetLayouts()?.ToArray();
+ if (layouts is null)
{
return BlockEditorData.Empty;
}
IEnumerable references = GetBlockReferences(layouts);
- return new BlockEditorData(references, value);
+ return new BlockEditorData(references, value!);
+ }
+
+ // this method is only meant to have any effect when migrating block editor values
+ // from the original format to the new, variant enabled format
+ private void AmendExpose(TValue value)
+ => value.Expose = value.ContentData.Select(cd => new BlockItemVariation(cd.Key, null, null)).ToList();
+
+ // this method is only meant to have any effect when migrating block editor values
+ // from the original format to the new, variant enabled format
+ private bool ConvertOriginalBlockFormat(List blockItemDatas)
+ {
+ var converted = false;
+ foreach (BlockItemData blockItemData in blockItemDatas)
+ {
+ // only overwrite the Properties collection if none have been added at this point
+ if (blockItemData.Values.Any() is false && blockItemData.RawPropertyValues.Any())
+ {
+ blockItemData.Values = blockItemData
+ .RawPropertyValues
+ .Select(item => new BlockPropertyValue { Alias = item.Key, Value = item.Value })
+ .ToList();
+ converted = true;
+ }
+
+ // no matter what, clear the RawPropertyValues collection so it is not saved back to the DB
+ blockItemData.RawPropertyValues.Clear();
+
+ // assign the correct Key if only a UDI is set
+ if (blockItemData.Key == Guid.Empty && blockItemData.Udi is GuidUdi guidUdi)
+ {
+ blockItemData.Key = guidUdi.Guid;
+ converted = true;
+ }
+
+ // no matter what, clear the UDI value so it's not saved back to the DB
+ blockItemData.Udi = null;
+ }
+
+ return converted;
}
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockGridEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockGridEditorDataConverter.cs
index b771ed1e3c..b2e3a1337d 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockGridEditorDataConverter.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockGridEditorDataConverter.cs
@@ -1,8 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
-using Microsoft.Extensions.DependencyInjection;
-using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Serialization;
namespace Umbraco.Cms.Core.Models.Blocks;
@@ -12,12 +10,6 @@ namespace Umbraco.Cms.Core.Models.Blocks;
///
public class BlockGridEditorDataConverter : BlockEditorDataConverter
{
- [Obsolete("Use the constructor that takes IJsonSerializer. Will be removed in V15.")]
- public BlockGridEditorDataConverter()
- : this(StaticServiceProvider.Instance.GetRequiredService())
- {
- }
-
public BlockGridEditorDataConverter(IJsonSerializer jsonSerializer)
: base(jsonSerializer)
{
@@ -27,7 +19,7 @@ public class BlockGridEditorDataConverter : BlockEditorDataConverter ExtractContentAndSettingsReferences(BlockGridLayoutItem item)
{
- var references = new List { new(item.ContentUdi, item.SettingsUdi) };
+ var references = new List { new(item.ContentKey, item.SettingsKey) };
references.AddRange(item.Areas.SelectMany(area => area.Items.SelectMany(ExtractContentAndSettingsReferences)));
return references;
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockGridItem.cs b/src/Umbraco.Core/Models/Blocks/BlockGridItem.cs
index abe8cc89a0..8c463187e3 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockGridItem.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockGridItem.cs
@@ -23,21 +23,45 @@ namespace Umbraco.Cms.Core.Models.Blocks
/// contentUdi
/// or
/// content
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public BlockGridItem(Udi contentUdi, IPublishedElement content, Udi settingsUdi, IPublishedElement settings)
+ : this(
+ (contentUdi as GuidUdi)?.Guid ?? throw new ArgumentException(nameof(contentUdi)),
+ content,
+ (settingsUdi as GuidUdi)?.Guid,
+ settings)
{
- ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi));
+ }
+
+ public BlockGridItem(Guid contentKey, IPublishedElement content, Guid? settingsKey, IPublishedElement? settings)
+ {
+ ContentKey = contentKey;
+ ContentUdi = new GuidUdi(Constants.UdiEntityType.Element, contentKey);
Content = content ?? throw new ArgumentNullException(nameof(content));
- SettingsUdi = settingsUdi;
+ SettingsKey = settingsKey;
+ SettingsUdi = settingsKey.HasValue
+ ? new GuidUdi(Constants.UdiEntityType.Element, settingsKey.Value)
+ : null;
Settings = settings;
}
+ ///
+ /// Gets the content key.
+ ///
+ public Guid ContentKey { get; set; }
+
+ ///
+ /// Gets the settings key.
+ ///
+ public Guid? SettingsKey { get; set; }
+
///
/// Gets the content UDI.
///
///
/// The content UDI.
///
- [DataMember(Name = "contentUdi")]
+ [Obsolete("Use ContentKey instead. Will be removed in V18.")]
public Udi ContentUdi { get; }
///
@@ -46,7 +70,6 @@ namespace Umbraco.Cms.Core.Models.Blocks
///
/// The content.
///
- [DataMember(Name = "content")]
public IPublishedElement Content { get; }
///
@@ -55,8 +78,8 @@ namespace Umbraco.Cms.Core.Models.Blocks
///
/// The settings UDI.
///
- [DataMember(Name = "settingsUdi")]
- public Udi SettingsUdi { get; }
+ [Obsolete("Use SettingsKey instead. Will be removed in V18.")]
+ public Udi? SettingsUdi { get; }
///
/// Gets the settings.
@@ -64,37 +87,31 @@ namespace Umbraco.Cms.Core.Models.Blocks
///
/// The settings.
///
- [DataMember(Name = "settings")]
- public IPublishedElement Settings { get; }
+ public IPublishedElement? Settings { get; }
///
/// The number of rows this item should span
///
- [DataMember(Name = "rowSpan")]
public int RowSpan { get; set; }
///
/// The number of columns this item should span
///
- [DataMember(Name = "columnSpan")]
public int ColumnSpan { get; set; }
///
/// The grid areas within this item
///
- [DataMember(Name = "areas")]
public IEnumerable Areas { get; set; } = Array.Empty();
///
/// The number of columns available for the areas to span
///
- [DataMember(Name = "areaGridColumns")]
public int? AreaGridColumns { get; set; }
///
/// The number of columns in the root grid
///
- [DataMember(Name = "gridColumns")]
public int? GridColumns { get; set; }
}
@@ -112,12 +129,19 @@ namespace Umbraco.Cms.Core.Models.Blocks
/// The content.
/// The settings UDI.
/// The settings.
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public BlockGridItem(Udi contentUdi, T content, Udi settingsUdi, IPublishedElement settings)
: base(contentUdi, content, settingsUdi, settings)
{
Content = content;
}
+ public BlockGridItem(Guid contentKey, T content, Guid? settingsKey, IPublishedElement? settings)
+ : base(contentKey, content, settingsKey, settings)
+ {
+ Content = content;
+ }
+
///
/// Gets the content.
///
@@ -143,18 +167,25 @@ namespace Umbraco.Cms.Core.Models.Blocks
/// The content.
/// The settings udi.
/// The settings.
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public BlockGridItem(Udi contentUdi, TContent content, Udi settingsUdi, TSettings settings)
: base(contentUdi, content, settingsUdi, settings)
{
Settings = settings;
}
+ public BlockGridItem(Guid contentKey, TContent content, Guid? settingsKey, TSettings? settings)
+ : base(contentKey, content, settingsKey, settings)
+ {
+ Settings = settings;
+ }
+
///
/// Gets the settings.
///
///
/// The settings.
///
- public new TSettings Settings { get; }
+ public new TSettings? Settings { get; }
}
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockGridLayoutItem.cs b/src/Umbraco.Core/Models/Blocks/BlockGridLayoutItem.cs
index bd24a0d5f5..ff977acf98 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockGridLayoutItem.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockGridLayoutItem.cs
@@ -6,12 +6,8 @@ namespace Umbraco.Cms.Core.Models.Blocks;
///
/// Used for deserializing the block grid layout
///
-public class BlockGridLayoutItem : IBlockLayoutItem
+public class BlockGridLayoutItem : BlockLayoutItemBase
{
- public Udi? ContentUdi { get; set; }
-
- public Udi? SettingsUdi { get; set; }
-
public int? ColumnSpan { get; set; }
public int? RowSpan { get; set; }
@@ -21,10 +17,25 @@ public class BlockGridLayoutItem : IBlockLayoutItem
public BlockGridLayoutItem()
{ }
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public BlockGridLayoutItem(Udi contentUdi)
- => ContentUdi = contentUdi;
+ : base(contentUdi)
+ {
+ }
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public BlockGridLayoutItem(Udi contentUdi, Udi settingsUdi)
- : this(contentUdi)
- => SettingsUdi = settingsUdi;
+ : base(contentUdi, settingsUdi)
+ {
+ }
+
+ public BlockGridLayoutItem(Guid contentKey)
+ : base(contentKey)
+ {
+ }
+
+ public BlockGridLayoutItem(Guid contentKey, Guid settingsKey)
+ : base(contentKey, settingsKey)
+ {
+ }
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockGridValue.cs b/src/Umbraco.Core/Models/Blocks/BlockGridValue.cs
index 650f4e4754..e7f7c1ca17 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockGridValue.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockGridValue.cs
@@ -1,3 +1,5 @@
+using System.Text.Json.Serialization;
+
namespace Umbraco.Cms.Core.Models.Blocks;
///
@@ -19,5 +21,6 @@ public class BlockGridValue : BlockValue
=> Layout[PropertyEditorAlias] = layouts;
///
+ [JsonIgnore]
public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.BlockGrid;
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockItemData.cs b/src/Umbraco.Core/Models/Blocks/BlockItemData.cs
index 75d34ba8c6..e99e8010e7 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockItemData.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockItemData.cs
@@ -14,10 +14,20 @@ public class BlockItemData
{
}
+ [Obsolete("Use constructor that accepts GUID key instead. Will be removed in V18.")]
public BlockItemData(Udi udi, Guid contentTypeKey, string contentTypeAlias)
+ : this(
+ (udi as GuidUdi)?.Guid ?? throw new ArgumentException(nameof(udi)),
+ contentTypeKey,
+ contentTypeAlias)
+ {
+ }
+
+ public BlockItemData(Guid key, Guid contentTypeKey, string contentTypeAlias)
{
ContentTypeAlias = contentTypeAlias;
- Udi = udi;
+ Key = key;
+ Udi = new GuidUdi(Constants.UdiEntityType.Element, key);
ContentTypeKey = contentTypeKey;
}
@@ -29,43 +39,14 @@ public class BlockItemData
[JsonIgnore]
public string ContentTypeAlias { get; set; } = string.Empty;
+ [Obsolete("Use Key instead. Will be removed in V18.")]
public Udi? Udi { get; set; }
- [JsonIgnore]
- public Guid Key => Udi is not null ? ((GuidUdi)Udi).Guid : throw new InvalidOperationException("No Udi assigned");
+ public Guid Key { get; set; }
- ///
- /// The remaining properties will be serialized to a dictionary
- ///
- ///
- /// The JsonExtensionDataAttribute is used to put the non-typed properties into a bucket
- /// https://docs.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonextensiondataattribute
- /// NestedContent serializes to string, int, whatever eg
- /// "stringValue":"Some String","numericValue":125,"otherNumeric":null
- ///
+ public IList Values { get; set; } = new List();
+
+ [Obsolete("Use Properties instead. Will be removed in V18.")]
[JsonExtensionData]
public Dictionary RawPropertyValues { get; set; } = new();
-
- ///
- /// Used during deserialization to convert the raw property data into data with a property type context
- ///
- [JsonIgnore]
- public IDictionary PropertyValues { get; set; } =
- new Dictionary();
-
- ///
- /// Used during deserialization to populate the property value/property type of a block item content property
- ///
- public class BlockPropertyValue
- {
- public BlockPropertyValue(object? value, IPropertyType propertyType)
- {
- Value = value;
- PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType));
- }
-
- public object? Value { get; }
-
- public IPropertyType PropertyType { get; }
- }
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockItemVariation.cs b/src/Umbraco.Core/Models/Blocks/BlockItemVariation.cs
new file mode 100644
index 0000000000..bb8dfcff22
--- /dev/null
+++ b/src/Umbraco.Core/Models/Blocks/BlockItemVariation.cs
@@ -0,0 +1,21 @@
+namespace Umbraco.Cms.Core.Models.Blocks;
+
+public class BlockItemVariation
+{
+ public BlockItemVariation()
+ {
+ }
+
+ public BlockItemVariation(Guid contentKey, string? culture, string? segment)
+ {
+ ContentKey = contentKey;
+ Culture = culture;
+ Segment = segment;
+ }
+
+ public Guid ContentKey { get; set; }
+
+ public string? Culture { get; set; }
+
+ public string? Segment { get; set; }
+}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockLayoutItemBase.cs b/src/Umbraco.Core/Models/Blocks/BlockLayoutItemBase.cs
new file mode 100644
index 0000000000..4f4ff9e22a
--- /dev/null
+++ b/src/Umbraco.Core/Models/Blocks/BlockLayoutItemBase.cs
@@ -0,0 +1,84 @@
+namespace Umbraco.Cms.Core.Models.Blocks;
+
+public abstract class BlockLayoutItemBase : IBlockLayoutItem
+{
+ private Guid? _contentKey;
+ private Guid? _settingsKey;
+
+ private Udi? _contentUdi;
+ private Udi? _settingsUdi;
+
+ [Obsolete("Use ContentKey instead. Will be removed in V18.")]
+ public Udi? ContentUdi
+ {
+ get => _contentUdi;
+ set
+ {
+ if (_contentKey is not null)
+ {
+ return;
+ }
+
+ _contentUdi = value;
+ _contentKey = (value as GuidUdi)?.Guid;
+ }
+ }
+
+ [Obsolete("Use SettingsKey instead. Will be removed in V18.")]
+ public Udi? SettingsUdi
+ {
+ get => _settingsUdi;
+ set
+ {
+ if (_settingsKey is not null)
+ {
+ return;
+ }
+
+ _settingsUdi = value;
+ _settingsKey = (value as GuidUdi)?.Guid;
+ }
+ }
+
+ public Guid ContentKey
+ {
+ get => _contentKey ?? throw new InvalidOperationException("ContentKey has not yet been initialized");
+ set => _contentKey = value;
+ }
+
+ public Guid? SettingsKey
+ {
+ get => _settingsKey;
+ set => _settingsKey = value;
+ }
+
+ protected BlockLayoutItemBase()
+ { }
+
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
+ protected BlockLayoutItemBase(Udi contentUdi)
+ : this((contentUdi as GuidUdi)?.Guid ?? throw new ArgumentException(nameof(contentUdi)))
+ {
+ }
+
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
+ protected BlockLayoutItemBase(Udi contentUdi, Udi settingsUdi)
+ : this(
+ (contentUdi as GuidUdi)?.Guid ?? throw new ArgumentException(nameof(contentUdi)),
+ (settingsUdi as GuidUdi)?.Guid ?? throw new ArgumentException(nameof(settingsUdi)))
+ {
+ }
+
+ protected BlockLayoutItemBase(Guid contentKey)
+ {
+ ContentKey = contentKey;
+ ContentUdi = new GuidUdi(Constants.UdiEntityType.Element, contentKey);
+ }
+
+ protected BlockLayoutItemBase(Guid contentKey, Guid settingsKey)
+ : this(contentKey)
+ {
+ SettingsKey = settingsKey;
+ SettingsUdi = new GuidUdi(Constants.UdiEntityType.Element, settingsKey);
+ }
+}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs
index 7842c66a28..e213be5f12 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs
@@ -1,8 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
-using Microsoft.Extensions.DependencyInjection;
-using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Serialization;
namespace Umbraco.Cms.Core.Models.Blocks;
@@ -12,17 +10,11 @@ namespace Umbraco.Cms.Core.Models.Blocks;
///
public class BlockListEditorDataConverter : BlockEditorDataConverter
{
- [Obsolete("Use the constructor that takes IJsonSerializer. Will be removed in V15.")]
- public BlockListEditorDataConverter()
- : this(StaticServiceProvider.Instance.GetRequiredService())
- {
- }
-
public BlockListEditorDataConverter(IJsonSerializer jsonSerializer)
- : base(Constants.PropertyEditors.Aliases.BlockList, jsonSerializer)
+ : base(jsonSerializer)
{
}
protected override IEnumerable GetBlockReferences(IEnumerable layout)
- => layout.Select(x => new ContentAndSettingsReference(x.ContentUdi, x.SettingsUdi)).ToList();
+ => layout.Select(x => new ContentAndSettingsReference(x.ContentKey, x.SettingsKey)).ToList();
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockListItem.cs b/src/Umbraco.Core/Models/Blocks/BlockListItem.cs
index 6ccc4080e2..7e14a1b1e8 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockListItem.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockListItem.cs
@@ -25,11 +25,25 @@ public class BlockListItem : IBlockReference
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public BlockListItem(Udi contentUdi, IPublishedElement content, Udi settingsUdi, IPublishedElement settings)
+ : this(
+ (contentUdi as GuidUdi)?.Guid ?? throw new ArgumentException(nameof(contentUdi)),
+ content,
+ (settingsUdi as GuidUdi)?.Guid,
+ settings)
{
- ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi));
+ }
+
+ public BlockListItem(Guid contentKey, IPublishedElement content, Guid? settingsKey, IPublishedElement? settings)
+ {
+ ContentKey = contentKey;
+ ContentUdi = new GuidUdi(Constants.UdiEntityType.Element, contentKey);
Content = content ?? throw new ArgumentNullException(nameof(content));
- SettingsUdi = settingsUdi;
+ SettingsKey = settingsKey;
+ SettingsUdi = settingsKey.HasValue
+ ? new GuidUdi(Constants.UdiEntityType.Element, settingsKey.Value)
+ : null;
Settings = settings;
}
@@ -39,7 +53,6 @@ public class BlockListItem : IBlockReference
/// The content.
///
- [DataMember(Name = "content")]
public IPublishedElement Content { get; }
///
@@ -48,8 +61,8 @@ public class BlockListItem : IBlockReference
/// The settings UDI.
///
- [DataMember(Name = "settingsUdi")]
- public Udi SettingsUdi { get; }
+ [Obsolete("Use SettingsKey instead. Will be removed in V18.")]
+ public Udi? SettingsUdi { get; }
///
/// Gets the content UDI.
@@ -57,17 +70,26 @@ public class BlockListItem : IBlockReference
/// The content UDI.
///
- [DataMember(Name = "contentUdi")]
+ [Obsolete("Use ContentKey instead. Will be removed in V18.")]
public Udi ContentUdi { get; }
+ ///
+ /// Gets the content key.
+ ///
+ public Guid ContentKey { get; set; }
+
+ ///
+ /// Gets the settings key.
+ ///
+ public Guid? SettingsKey { get; set; }
+
///
/// Gets the settings.
///
///
/// The settings.
///
- [DataMember(Name = "settings")]
- public IPublishedElement Settings { get; }
+ public IPublishedElement? Settings { get; }
}
///
@@ -85,10 +107,15 @@ public class BlockListItem : BlockListItem
/// The content.
/// The settings UDI.
/// The settings.
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public BlockListItem(Udi contentUdi, T content, Udi settingsUdi, IPublishedElement settings)
: base(contentUdi, content, settingsUdi, settings) =>
Content = content;
+ public BlockListItem(Guid contentKey, T content, Guid? settingsKey, IPublishedElement? settings)
+ : base(contentKey, content, settingsKey, settings) =>
+ Content = content;
+
///
/// Gets the content.
///
@@ -115,15 +142,20 @@ public class BlockListItem : BlockListItem
/// The content.
/// The settings udi.
/// The settings.
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public BlockListItem(Udi contentUdi, TContent content, Udi settingsUdi, TSettings settings)
: base(contentUdi, content, settingsUdi, settings) =>
Settings = settings;
+ public BlockListItem(Guid contentKey, TContent content, Guid? settingsKey, TSettings? settings)
+ : base(contentKey, content, settingsKey, settings) =>
+ Settings = settings;
+
///
/// Gets the settings.
///
///
/// The settings.
///
- public new TSettings Settings { get; }
+ public new TSettings? Settings { get; }
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs b/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs
index 4412257add..cffa234156 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs
@@ -6,19 +6,30 @@ namespace Umbraco.Cms.Core.Models.Blocks;
///
/// Used for deserializing the block list layout
///
-public class BlockListLayoutItem : IBlockLayoutItem
+public class BlockListLayoutItem : BlockLayoutItemBase
{
- public Udi? ContentUdi { get; set; }
-
- public Udi? SettingsUdi { get; set; }
-
public BlockListLayoutItem()
{ }
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public BlockListLayoutItem(Udi contentUdi)
- => ContentUdi = contentUdi;
+ : base(contentUdi)
+ {
+ }
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public BlockListLayoutItem(Udi contentUdi, Udi settingsUdi)
- : this(contentUdi)
- => SettingsUdi = settingsUdi;
+ : base(contentUdi, settingsUdi)
+ {
+ }
+
+ public BlockListLayoutItem(Guid contentKey)
+ : base(contentKey)
+ {
+ }
+
+ public BlockListLayoutItem(Guid contentKey, Guid settingsKey)
+ : base(contentKey, settingsKey)
+ {
+ }
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockListValue.cs b/src/Umbraco.Core/Models/Blocks/BlockListValue.cs
index d06277f71f..fe6524f88d 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockListValue.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockListValue.cs
@@ -1,3 +1,5 @@
+using System.Text.Json.Serialization;
+
namespace Umbraco.Cms.Core.Models.Blocks;
///
@@ -19,5 +21,6 @@ public class BlockListValue : BlockValue
=> Layout[PropertyEditorAlias] = layouts;
///
+ [JsonIgnore]
public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.BlockList;
}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockPropertyValue.cs b/src/Umbraco.Core/Models/Blocks/BlockPropertyValue.cs
new file mode 100644
index 0000000000..0b27f16681
--- /dev/null
+++ b/src/Umbraco.Core/Models/Blocks/BlockPropertyValue.cs
@@ -0,0 +1,13 @@
+using System.Text.Json.Serialization;
+using Umbraco.Cms.Core.Models.ContentEditing;
+
+namespace Umbraco.Cms.Core.Models.Blocks;
+
+public sealed class BlockPropertyValue : ValueModelBase
+{
+ // Used during deserialization to populate the property value/property type of a block item content property
+ [JsonIgnore]
+ public IPropertyType? PropertyType { get; set; }
+
+ public string? EditorAlias => PropertyType?.PropertyEditorAlias;
+}
diff --git a/src/Umbraco.Core/Models/Blocks/BlockValue.cs b/src/Umbraco.Core/Models/Blocks/BlockValue.cs
index bfe84430f8..1c25c8d434 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockValue.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockValue.cs
@@ -32,6 +32,14 @@ public abstract class BlockValue
///
public List SettingsData { get; set; } = [];
+ ///
+ /// Gets or sets the availability of blocks per variation.
+ ///
+ ///
+ /// Only applicable for block level variance.
+ ///
+ public IList Expose { get; set; } = new List();
+
///
/// Gets the property editor alias of the current layout.
///
@@ -39,6 +47,9 @@ public abstract class BlockValue
/// The property editor alias of the current layout.
///
public abstract string PropertyEditorAlias { get; }
+
+ [Obsolete("Will be removed in V18.")]
+ public virtual bool SupportsBlockLayoutAlias(string alias) => alias.Equals(PropertyEditorAlias);
}
///
diff --git a/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs b/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs
index 61b95235cd..f26db3b46d 100644
--- a/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs
+++ b/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs
@@ -5,16 +5,34 @@ namespace Umbraco.Cms.Core.Models.Blocks;
public struct ContentAndSettingsReference : IEquatable
{
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public ContentAndSettingsReference(Udi? contentUdi, Udi? settingsUdi)
+ : this(
+ (contentUdi as GuidUdi)?.Guid ?? throw new ArgumentException(nameof(contentUdi)),
+ (settingsUdi as GuidUdi)?.Guid)
{
- ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi));
- SettingsUdi = settingsUdi;
}
+ public ContentAndSettingsReference(Guid contentKey, Guid? settingsKey)
+ {
+ ContentKey = contentKey;
+ SettingsKey = settingsKey;
+ ContentUdi = new GuidUdi(Constants.UdiEntityType.Element, contentKey);
+ SettingsUdi = settingsKey.HasValue
+ ? new GuidUdi(Constants.UdiEntityType.Element, settingsKey.Value)
+ : null;
+ }
+
+ [Obsolete("Use ContentKey instead. Will be removed in V18.")]
public Udi ContentUdi { get; }
+ [Obsolete("Use SettingsKey instead. Will be removed in V18.")]
public Udi? SettingsUdi { get; }
+ public Guid ContentKey { get; set; }
+
+ public Guid? SettingsKey { get; set; }
+
public static bool operator ==(ContentAndSettingsReference left, ContentAndSettingsReference right) =>
left.Equals(right);
diff --git a/src/Umbraco.Core/Models/Blocks/IBlockLayoutItem.cs b/src/Umbraco.Core/Models/Blocks/IBlockLayoutItem.cs
index eb5d3b0553..3974bfc1a0 100644
--- a/src/Umbraco.Core/Models/Blocks/IBlockLayoutItem.cs
+++ b/src/Umbraco.Core/Models/Blocks/IBlockLayoutItem.cs
@@ -5,7 +5,13 @@ namespace Umbraco.Cms.Core.Models.Blocks;
public interface IBlockLayoutItem
{
+ [Obsolete("Use ContentKey instead. Will be removed in V18.")]
public Udi? ContentUdi { get; set; }
+ [Obsolete("Use SettingsKey instead. Will be removed in V18.")]
public Udi? SettingsUdi { get; set; }
+
+ public Guid ContentKey { get; set; }
+
+ public Guid? SettingsKey { get; set; }
}
diff --git a/src/Umbraco.Core/Models/Blocks/IBlockReference.cs b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs
index 647d1f5b2f..2505183efa 100644
--- a/src/Umbraco.Core/Models/Blocks/IBlockReference.cs
+++ b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs
@@ -37,7 +37,7 @@ public interface IBlockReference : IBlockReference
///
/// The settings.
///
- TSettings Settings { get; }
+ TSettings? Settings { get; }
}
diff --git a/src/Umbraco.Core/Models/Blocks/RichTextBlockItem.cs b/src/Umbraco.Core/Models/Blocks/RichTextBlockItem.cs
index f5be6f9e23..17abbe1d90 100644
--- a/src/Umbraco.Core/Models/Blocks/RichTextBlockItem.cs
+++ b/src/Umbraco.Core/Models/Blocks/RichTextBlockItem.cs
@@ -25,21 +25,38 @@ public class RichTextBlockItem : IBlockReference
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public RichTextBlockItem(Udi contentUdi, IPublishedElement content, Udi settingsUdi, IPublishedElement settings)
+ : this(
+ (contentUdi as GuidUdi)?.Guid ?? throw new ArgumentException(nameof(contentUdi)),
+ content,
+ (settingsUdi as GuidUdi)?.Guid,
+ settings)
{
- ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi));
+ }
+
+ public RichTextBlockItem(Guid contentKey, IPublishedElement content, Guid? settingsKey, IPublishedElement? settings)
+ {
+ ContentKey = contentKey;
+ ContentUdi = new GuidUdi(Constants.UdiEntityType.Element, contentKey);
Content = content ?? throw new ArgumentNullException(nameof(content));
- SettingsUdi = settingsUdi;
+ SettingsKey = settingsKey;
+ SettingsUdi = settingsKey.HasValue
+ ? new GuidUdi(Constants.UdiEntityType.Element, settingsKey.Value)
+ : null;
Settings = settings;
}
+ public Guid ContentKey { get; set; }
+
+ public Guid? SettingsKey { get; set; }
+
///
/// Gets the content.
///
///
/// The content.
///
- [DataMember(Name = "content")]
public IPublishedElement Content { get; }
///
@@ -48,8 +65,8 @@ public class RichTextBlockItem : IBlockReference
/// The settings UDI.
///
- [DataMember(Name = "settingsUdi")]
- public Udi SettingsUdi { get; }
+ [Obsolete("Use SettingsKey instead. Will be removed in V18.")]
+ public Udi? SettingsUdi { get; }
///
/// Gets the content UDI.
@@ -57,7 +74,7 @@ public class RichTextBlockItem : IBlockReference
/// The content UDI.
///
- [DataMember(Name = "contentUdi")]
+ [Obsolete("Use ContentKey instead. Will be removed in V18.")]
public Udi ContentUdi { get; }
///
@@ -66,8 +83,7 @@ public class RichTextBlockItem : IBlockReference
/// The settings.
///
- [DataMember(Name = "settings")]
- public IPublishedElement Settings { get; }
+ public IPublishedElement? Settings { get; }
}
///
@@ -85,10 +101,15 @@ public class RichTextBlockItem : RichTextBlockItem
/// The content.
/// The settings UDI.
/// The settings.
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public RichTextBlockItem(Udi contentUdi, T content, Udi settingsUdi, IPublishedElement settings)
: base(contentUdi, content, settingsUdi, settings) =>
Content = content;
+ public RichTextBlockItem(Guid contentKey, T content, Guid? settingsKey, IPublishedElement? settings)
+ : base(contentKey, content, settingsKey, settings) =>
+ Content = content;
+
///
/// Gets the content.
///
@@ -115,15 +136,20 @@ public class RichTextBlockItem : RichTextBlockItemThe content.
/// The settings udi.
/// The settings.
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public RichTextBlockItem(Udi contentUdi, TContent content, Udi settingsUdi, TSettings settings)
: base(contentUdi, content, settingsUdi, settings) =>
Settings = settings;
+ public RichTextBlockItem(Guid contentKey, TContent content, Guid? settingsKey, TSettings? settings)
+ : base(contentKey, content, settingsKey, settings) =>
+ Settings = settings;
+
///
/// Gets the settings.
///
///
/// The settings.
///
- public new TSettings Settings { get; }
+ public new TSettings? Settings { get; }
}
diff --git a/src/Umbraco.Core/Models/Blocks/RichTextBlockLayoutItem.cs b/src/Umbraco.Core/Models/Blocks/RichTextBlockLayoutItem.cs
index 0cd7210443..57d69003a1 100644
--- a/src/Umbraco.Core/Models/Blocks/RichTextBlockLayoutItem.cs
+++ b/src/Umbraco.Core/Models/Blocks/RichTextBlockLayoutItem.cs
@@ -6,19 +6,30 @@ namespace Umbraco.Cms.Core.Models.Blocks;
///
/// Used for deserializing the rich text block layouts
///
-public class RichTextBlockLayoutItem : IBlockLayoutItem
+public class RichTextBlockLayoutItem : BlockLayoutItemBase
{
- public Udi? ContentUdi { get; set; }
-
- public Udi? SettingsUdi { get; set; }
-
public RichTextBlockLayoutItem()
{ }
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public RichTextBlockLayoutItem(Udi contentUdi)
- => ContentUdi = contentUdi;
+ : base(contentUdi)
+ {
+ }
+ [Obsolete("Use constructor that accepts GUIDs instead. Will be removed in V18.")]
public RichTextBlockLayoutItem(Udi contentUdi, Udi settingsUdi)
- : this(contentUdi)
- => SettingsUdi = settingsUdi;
+ : base(contentUdi, settingsUdi)
+ {
+ }
+
+ public RichTextBlockLayoutItem(Guid contentKey)
+ : base(contentKey)
+ {
+ }
+
+ public RichTextBlockLayoutItem(Guid contentKey, Guid settingsKey)
+ : base(contentKey, settingsKey)
+ {
+ }
}
diff --git a/src/Umbraco.Core/Models/Blocks/RichTextBlockValue.cs b/src/Umbraco.Core/Models/Blocks/RichTextBlockValue.cs
index 728c06152e..efae15d0a1 100644
--- a/src/Umbraco.Core/Models/Blocks/RichTextBlockValue.cs
+++ b/src/Umbraco.Core/Models/Blocks/RichTextBlockValue.cs
@@ -1,3 +1,5 @@
+using System.Text.Json.Serialization;
+
namespace Umbraco.Cms.Core.Models.Blocks;
///
@@ -19,5 +21,11 @@ public class RichTextBlockValue : BlockValue
=> Layout[PropertyEditorAlias] = layouts;
///
- public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.TinyMce;
+ [JsonIgnore]
+ public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.RichText;
+
+ // RTE block layouts uses "Umbraco.TinyMCE" in V14 and below, but should use "Umbraco.RichText" for V15+
+ [Obsolete("Will be removed in V18.")]
+ public override bool SupportsBlockLayoutAlias(string alias)
+ => base.SupportsBlockLayoutAlias(alias) || alias.Equals(Constants.PropertyEditors.Aliases.TinyMce);
}
diff --git a/src/Umbraco.Core/Models/Blocks/RichTextEditorBlockDataConverter.cs b/src/Umbraco.Core/Models/Blocks/RichTextEditorBlockDataConverter.cs
index 183dabe420..508b55202c 100644
--- a/src/Umbraco.Core/Models/Blocks/RichTextEditorBlockDataConverter.cs
+++ b/src/Umbraco.Core/Models/Blocks/RichTextEditorBlockDataConverter.cs
@@ -7,17 +7,11 @@ namespace Umbraco.Cms.Core.Models.Blocks;
///
public sealed class RichTextEditorBlockDataConverter : BlockEditorDataConverter
{
- [Obsolete("Use the constructor that takes IJsonSerializer. Will be removed in V15.")]
- public RichTextEditorBlockDataConverter()
- : base(Constants.PropertyEditors.Aliases.TinyMce)
- {
- }
-
public RichTextEditorBlockDataConverter(IJsonSerializer jsonSerializer)
: base(jsonSerializer)
{
}
protected override IEnumerable GetBlockReferences(IEnumerable layout)
- => layout.Select(x => new ContentAndSettingsReference(x.ContentUdi, x.SettingsUdi)).ToList();
+ => layout.Select(x => new ContentAndSettingsReference(x.ContentKey, x.SettingsKey)).ToList();
}
diff --git a/src/Umbraco.Core/Models/ContentBaseExtensions.cs b/src/Umbraco.Core/Models/ContentBaseExtensions.cs
index 656db0f82f..09aeee2f7d 100644
--- a/src/Umbraco.Core/Models/ContentBaseExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentBaseExtensions.cs
@@ -17,8 +17,9 @@ public static class ContentBaseExtensions
///
///
/// The culture.
+ /// Whether to get the published or draft.
/// The URL segment.
- public static string? GetUrlSegment(this IContentBase content, IShortStringHelper shortStringHelper, IEnumerable urlSegmentProviders, string? culture = null)
+ public static string? GetUrlSegment(this IContentBase content, IShortStringHelper shortStringHelper, IEnumerable urlSegmentProviders, string? culture = null, bool published = true)
{
if (content == null)
{
@@ -30,7 +31,7 @@ public static class ContentBaseExtensions
throw new ArgumentNullException(nameof(urlSegmentProviders));
}
- var url = urlSegmentProviders.Select(p => p.GetUrlSegment(content, culture)).FirstOrDefault(u => u != null);
+ var url = urlSegmentProviders.Select(p => p.GetUrlSegment(content, published, culture)).FirstOrDefault(u => u != null);
if (url == null)
{
if (_defaultUrlSegmentProvider == null)
diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs
index 38d97febd5..233a67fa62 100644
--- a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs
@@ -1,4 +1,7 @@
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.PropertyEditors;
namespace Umbraco.Extensions;
@@ -206,7 +209,7 @@ public static class ContentRepositoryExtensions
{
foreach (IPropertyValue pvalue in otherProperty.Values)
{
- if (((otherProperty?.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, true) ?? false) &&
+ if (((otherProperty?.PropertyType?.SupportsVariation(pvalue.Culture, pvalue.Segment, true) ?? false) &&
(culture == "*" ||(pvalue.Culture?.InvariantEquals(culture) ?? false))) ||
otherProperty?.PropertyType?.Variations == ContentVariation.Nothing)
{
@@ -287,16 +290,22 @@ public static class ContentRepositoryExtensions
}
}
+ [Obsolete("Please use the overload that accepts all parameters. Will be removed in V16.")]
+ public static bool PublishCulture(this IContent content, CultureImpact? impact)
+ => PublishCulture(content, impact, DateTime.Now, StaticServiceProvider.Instance.GetRequiredService());
+
///
/// Sets the publishing values for names and properties.
///
///
///
+ ///
+ ///
///
/// A value indicating whether it was possible to publish the names and values for the specified
/// culture(s). The method may fail if required names are not set, but it does NOT validate property data
///
- public static bool PublishCulture(this IContent content, CultureImpact? impact)
+ public static bool PublishCulture(this IContent content, CultureImpact? impact, DateTime publishTime, PropertyEditorCollection propertyEditorCollection)
{
if (impact == null)
{
@@ -323,7 +332,7 @@ public static class ContentRepositoryExtensions
return false;
}
- content.SetPublishInfo(culture, name, DateTime.Now);
+ content.SetPublishInfo(culture, name, publishTime);
}
}
else if (impact.ImpactsOnlyInvariantCulture)
@@ -342,7 +351,7 @@ public static class ContentRepositoryExtensions
return false;
}
- content.SetPublishInfo(impact.Culture, name, DateTime.Now);
+ content.SetPublishInfo(impact.Culture, name, publishTime);
}
// set values
@@ -351,13 +360,13 @@ public static class ContentRepositoryExtensions
foreach (IProperty property in content.Properties)
{
// for the specified culture (null or all or specific)
- property.PublishValues(impact.Culture);
+ PublishPropertyValues(content, property, impact.Culture, propertyEditorCollection);
// maybe the specified culture did not impact the invariant culture, so PublishValues
// above would skip it, yet it *also* impacts invariant properties
if (impact.ImpactsAlsoInvariantProperties && (property.PropertyType.VariesByCulture() is false || impact.ImpactsOnlyDefaultCulture))
{
- property.PublishValues(null);
+ PublishPropertyValues(content, property, null, propertyEditorCollection);
}
}
@@ -365,6 +374,22 @@ public static class ContentRepositoryExtensions
return true;
}
+ private static void PublishPropertyValues(IContent content, IProperty property, string? culture, PropertyEditorCollection propertyEditorCollection)
+ {
+ // if the content varies by culture, let data editor opt-in to perform partial property publishing (per culture)
+ if (content.ContentType.VariesByCulture()
+ && propertyEditorCollection.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor)
+ && dataEditor.CanMergePartialPropertyValues(property.PropertyType))
+ {
+ // perform partial publishing for the current culture
+ property.PublishPartialValues(dataEditor, culture);
+ return;
+ }
+
+ // for the specified culture (null or all or specific)
+ property.PublishValues(culture);
+ }
+
///
/// Returns false if the culture is already unpublished
///
diff --git a/src/Umbraco.Core/Models/IProperty.cs b/src/Umbraco.Core/Models/IProperty.cs
index 54f1e8581f..d9a57e2558 100644
--- a/src/Umbraco.Core/Models/IProperty.cs
+++ b/src/Umbraco.Core/Models/IProperty.cs
@@ -1,4 +1,5 @@
using Umbraco.Cms.Core.Models.Entities;
+using Umbraco.Cms.Core.PropertyEditors;
namespace Umbraco.Cms.Core.Models;
@@ -35,5 +36,7 @@ public interface IProperty : IEntity, IRememberBeingDirty
void PublishValues(string? culture = "*", string segment = "*");
+ void PublishPartialValues(IDataEditor dataEditor, string? culture);
+
void UnpublishValues(string? culture = "*", string segment = "*");
}
diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs
index a4e8eb056c..b756b143ad 100644
--- a/src/Umbraco.Core/Models/Property.cs
+++ b/src/Umbraco.Core/Models/Property.cs
@@ -2,6 +2,7 @@
using System.Runtime.Serialization;
using Umbraco.Cms.Core.Collections;
using Umbraco.Cms.Core.Models.Entities;
+using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models;
@@ -174,6 +175,21 @@ public class Property : EntityBase, IProperty
: null;
}
+ // internal - must be invoked by the content item
+ // does *not* validate the value - content item must validate first
+ public void PublishPartialValues(IDataEditor dataEditor, string? culture)
+ {
+ if (PropertyType.VariesByCulture())
+ {
+ throw new NotSupportedException("Cannot publish merged culture values for culture variant properties");
+ }
+
+ culture = culture?.NullOrWhiteSpaceAsNull();
+
+ var value = dataEditor.MergePartialPropertyValueForCulture(_pvalue?.EditedValue, _pvalue?.PublishedValue, culture);
+ PublishValue(_pvalue, value);
+ }
+
// internal - must be invoked by the content item
// does *not* validate the value - content item must validate first
public void PublishValues(string? culture = "*", string? segment = "*")
@@ -300,13 +316,23 @@ public class Property : EntityBase, IProperty
return;
}
+ PublishValue(pvalue, ConvertAssignedValue(pvalue.EditedValue));
+ }
+
+ private void PublishValue(IPropertyValue? pvalue, object? newPublishedValue)
+ {
+ if (pvalue == null)
+ {
+ return;
+ }
+
if (!PropertyType.SupportsPublishing)
{
throw new NotSupportedException("Property type does not support publishing.");
}
var origValue = pvalue.PublishedValue;
- pvalue.PublishedValue = ConvertAssignedValue(pvalue.EditedValue);
+ pvalue.PublishedValue = newPublishedValue;
DetectChanges(pvalue.EditedValue, origValue, nameof(Values), PropertyValueComparer, false);
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs
index 9028a501ad..8792baaecc 100644
--- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs
@@ -100,6 +100,7 @@ public interface IPublishedContent : IPublishedElement
/// Gets the parent of the content item.
///
/// The parent of root content is null.
+ [Obsolete("Please use IDocumentNavigationQueryService.TryGetParentKey() instead. Scheduled for removal in V16.")]
IPublishedContent? Parent { get; }
///
@@ -141,10 +142,6 @@ public interface IPublishedContent : IPublishedElement
///
/// Gets the children of the content item that are available for the current culture.
///
+ [Obsolete("Please use IDocumentNavigationQueryService.TryGetChildrenKeys() instead. Scheduled for removal in V16.")]
IEnumerable Children { get; }
-
- ///
- /// Gets all the children of the content item, regardless of whether they are available for the current culture.
- ///
- IEnumerable ChildrenForAllCultures { get; }
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs
index c9394e1e27..56f7789578 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs
@@ -1,4 +1,8 @@
using System.Diagnostics;
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Cms.Core.PublishedCache;
+using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models.PublishedContent
@@ -33,9 +37,11 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
public abstract int SortOrder { get; }
///
+ [Obsolete("Not supported for members, scheduled for removal in v17")]
public abstract int Level { get; }
///
+ [Obsolete("Not supported for members, scheduled for removal in v17")]
public abstract string Path { get; }
///
@@ -66,18 +72,41 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
public abstract bool IsPublished(string? culture = null);
///
+ [Obsolete("Please use TryGetParentKey() on IDocumentNavigationQueryService or IMediaNavigationQueryService instead. Scheduled for removal in V16.")]
public abstract IPublishedContent? Parent { get; }
+ // FIXME
///
- public virtual IEnumerable Children => this.Children(_variationContextAccessor);
+ [Obsolete("Please use TryGetChildrenKeys() on IDocumentNavigationQueryService or IMediaNavigationQueryService instead. Scheduled for removal in V16.")]
+ public virtual IEnumerable Children => GetChildren();
- ///
- public abstract IEnumerable ChildrenForAllCultures { get; }
///
public abstract IEnumerable Properties { get; }
///
public abstract IPublishedProperty? GetProperty(string alias);
+
+ private IEnumerable GetChildren()
+ {
+ INavigationQueryService? navigationQueryService;
+ IPublishedCache? publishedCache;
+
+ switch (ContentType.ItemType)
+ {
+ case PublishedItemType.Content:
+ publishedCache = StaticServiceProvider.Instance.GetRequiredService();
+ navigationQueryService = StaticServiceProvider.Instance.GetRequiredService();
+ break;
+ case PublishedItemType.Media:
+ publishedCache = StaticServiceProvider.Instance.GetRequiredService();
+ navigationQueryService = StaticServiceProvider.Instance.GetRequiredService();
+ break;
+ default:
+ throw new NotImplementedException("Level is not implemented for " + ContentType.ItemType);
+ }
+
+ return this.Children(_variationContextAccessor, publishedCache, navigationQueryService);
+ }
}
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs
index ddeff558dd..807943edff 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs
@@ -1,4 +1,9 @@
using System.Diagnostics;
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Cms.Core.PublishedCache;
+using Umbraco.Cms.Core.Services.Navigation;
+using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models.PublishedContent;
@@ -90,6 +95,7 @@ public abstract class PublishedContentWrapped : IPublishedContent
public virtual PublishedItemType ItemType => _content.ItemType;
///
+ [Obsolete("Please use TryGetParentKey() on IDocumentNavigationQueryService or IMediaNavigationQueryService instead. Scheduled for removal in V16.")]
public virtual IPublishedContent? Parent => _content.Parent;
///
@@ -99,11 +105,9 @@ public abstract class PublishedContentWrapped : IPublishedContent
public virtual bool IsPublished(string? culture = null) => _content.IsPublished(culture);
///
+ [Obsolete("Please use TryGetChildrenKeys() on IDocumentNavigationQueryService or IMediaNavigationQueryService instead. Scheduled for removal in V16.")]
public virtual IEnumerable Children => _content.Children;
- ///
- public virtual IEnumerable ChildrenForAllCultures => _content.ChildrenForAllCultures;
-
///
public virtual IEnumerable Properties => _content.Properties;
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs
index 8a50323f12..bec5250e01 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs
@@ -1,4 +1,8 @@
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
+using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models.PublishedContent;
@@ -184,7 +188,7 @@ public class PublishedValueFallback : IPublishedValueFallback
IPublishedProperty? property; // if we are here, content's property has no value
do
{
- content = content?.Parent;
+ content = content?.Parent(StaticServiceProvider.Instance.GetRequiredService(), StaticServiceProvider.Instance.GetRequiredService());
IPublishedPropertyType? propertyType = content?.ContentType.GetPropertyType(alias);
diff --git a/src/Umbraco.Core/Models/PublishedDocumentUrlSegment.cs b/src/Umbraco.Core/Models/PublishedDocumentUrlSegment.cs
new file mode 100644
index 0000000000..81451d3223
--- /dev/null
+++ b/src/Umbraco.Core/Models/PublishedDocumentUrlSegment.cs
@@ -0,0 +1,9 @@
+namespace Umbraco.Cms.Core.Models;
+
+public class PublishedDocumentUrlSegment
+{
+ public required Guid DocumentKey { get; set; }
+ public required int LanguageId { get; set; }
+ public required string UrlSegment { get; set; }
+ public required bool IsDraft { get; set; }
+}
diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
index 24e1e62894..c275fdd108 100644
--- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
+++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
@@ -32,6 +32,7 @@ public static partial class Constants
public const string Document = TableNamePrefix + "Document";
public const string DocumentCultureVariation = TableNamePrefix + "DocumentCultureVariation";
public const string DocumentVersion = TableNamePrefix + "DocumentVersion";
+ public const string DocumentUrl = TableNamePrefix + "DocumentUrl";
public const string MediaVersion = TableNamePrefix + "MediaVersion";
public const string ContentSchedule = TableNamePrefix + "ContentSchedule";
diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentUrlRepository.cs
new file mode 100644
index 0000000000..5a3786aa12
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentUrlRepository.cs
@@ -0,0 +1,10 @@
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Core.Persistence.Repositories;
+
+public interface IDocumentUrlRepository
+{
+ void Save(IEnumerable publishedDocumentUrlSegments);
+ IEnumerable GetAll();
+ void DeleteByDocumentKey(IEnumerable select);
+}
diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs
index 3048162891..cb7a43ba78 100644
--- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs
@@ -196,4 +196,10 @@ public class DataEditor : IDataEditor
/// Provides a summary of the PropertyEditor for use with the .
///
protected virtual string DebuggerDisplay() => $"Alias: {Alias}";
+
+ ///
+ public virtual bool CanMergePartialPropertyValues(IPropertyType propertyType) => false;
+
+ ///
+ public virtual object? MergePartialPropertyValueForCulture(object? sourceValue, object? targetValue, string? culture) => sourceValue;
}
diff --git a/src/Umbraco.Core/PropertyEditors/IDataEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataEditor.cs
index 5a95445fce..a9d3b896e7 100644
--- a/src/Umbraco.Core/PropertyEditors/IDataEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/IDataEditor.cs
@@ -51,4 +51,18 @@ public interface IDataEditor : IDiscoverable
/// Is expected to throw if the editor does not support being configured, e.g. for most parameter editors.
///
IConfigurationEditor GetConfigurationEditor();
+
+ ///
+ /// Determines if the value editor needs to perform for a given property type.
+ ///
+ bool CanMergePartialPropertyValues(IPropertyType propertyType) => false;
+
+ ///
+ /// Partially merges a source property value into a target property value for a given culture.
+ ///
+ /// The source property value.
+ /// The target property value.
+ /// The culture (or null for invariant).
+ /// The result of the merge operation.
+ object? MergePartialPropertyValueForCulture(object? sourceValue, object? targetValue, string? culture) => sourceValue;
}
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs
index 014a0a1a8c..6fdf5d3246 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs
@@ -12,17 +12,14 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
public class MemberPickerValueConverter : PropertyValueConverterBase, IDeliveryApiPropertyValueConverter
{
private readonly IMemberService _memberService;
- private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
- private readonly IUmbracoContextAccessor _umbracoContextAccessor;
+ private readonly IPublishedMemberCache _memberCache;
public MemberPickerValueConverter(
IMemberService memberService,
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
- IUmbracoContextAccessor umbracoContextAccessor)
+ IPublishedMemberCache memberCache)
{
_memberService = memberService;
- _publishedSnapshotAccessor = publishedSnapshotAccessor;
- _umbracoContextAccessor = umbracoContextAccessor;
+ _memberCache = memberCache;
}
public override bool IsConverter(IPublishedPropertyType propertyType)
@@ -64,7 +61,6 @@ public class MemberPickerValueConverter : PropertyValueConverterBase, IDeliveryA
}
IPublishedContent? member;
- IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
if (source is int id)
{
IMember? m = _memberService.GetById(id);
@@ -73,7 +69,7 @@ public class MemberPickerValueConverter : PropertyValueConverterBase, IDeliveryA
return null;
}
- member = publishedSnapshot?.Members?.Get(m);
+ member = _memberCache.Get(m);
if (member != null)
{
return member;
@@ -92,7 +88,7 @@ public class MemberPickerValueConverter : PropertyValueConverterBase, IDeliveryA
return null;
}
- member = publishedSnapshot?.Members?.Get(m);
+ member = _memberCache.Get(m);
if (member != null)
{
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs
index 2d4060a89d..22e3067244 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs
@@ -25,24 +25,29 @@ public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase, IDe
};
private readonly IMemberService _memberService;
- private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly IApiContentBuilder _apiContentBuilder;
private readonly IApiMediaBuilder _apiMediaBuilder;
+ private readonly IPublishedContentCache _contentCache;
+ private readonly IPublishedMediaCache _mediaCache;
+ private readonly IPublishedMemberCache _memberCache;
public MultiNodeTreePickerValueConverter(
- IPublishedSnapshotAccessor publishedSnapshotAccessor,
IUmbracoContextAccessor umbracoContextAccessor,
IMemberService memberService,
IApiContentBuilder apiContentBuilder,
- IApiMediaBuilder apiMediaBuilder)
+ IApiMediaBuilder apiMediaBuilder,
+ IPublishedContentCache contentCache,
+ IPublishedMediaCache mediaCache,
+ IPublishedMemberCache memberCache)
{
- _publishedSnapshotAccessor = publishedSnapshotAccessor ??
- throw new ArgumentNullException(nameof(publishedSnapshotAccessor));
_umbracoContextAccessor = umbracoContextAccessor;
_memberService = memberService;
_apiContentBuilder = apiContentBuilder;
_apiMediaBuilder = apiMediaBuilder;
+ _contentCache = contentCache;
+ _mediaCache = mediaCache;
+ _memberCache = memberCache;
}
public override bool IsConverter(IPublishedPropertyType propertyType) =>
@@ -95,7 +100,6 @@ public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase, IDe
var multiNodeTreePicker = new List();
UmbracoObjectTypes objectType = UmbracoObjectTypes.Unknown;
- IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
foreach (Udi udi in udis)
{
if (udi is not GuidUdi guidUdi)
@@ -111,14 +115,14 @@ public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase, IDe
udi,
ref objectType,
UmbracoObjectTypes.Document,
- id => publishedSnapshot.Content?.GetById(guidUdi.Guid));
+ id => _contentCache.GetById(guidUdi.Guid));
break;
case Constants.UdiEntityType.Media:
multiNodeTreePickerItem = GetPublishedContent(
udi,
ref objectType,
UmbracoObjectTypes.Media,
- id => publishedSnapshot.Media?.GetById(guidUdi.Guid));
+ id => _mediaCache.GetById(guidUdi.Guid));
break;
case Constants.UdiEntityType.Member:
multiNodeTreePickerItem = GetPublishedContent(
@@ -133,7 +137,7 @@ public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase, IDe
return null;
}
- IPublishedContent? member = publishedSnapshot?.Members?.Get(m);
+ IPublishedContent? member = _memberCache.Get(m);
return member;
});
break;
@@ -188,8 +192,6 @@ public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase, IDe
return DefaultValue();
}
- IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
-
var entityType = GetEntityType(propertyType);
if (entityType == "content")
@@ -203,14 +205,14 @@ public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase, IDe
{
Constants.UdiEntityType.Document => entityTypeUdis.Select(udi =>
{
- IPublishedContent? content = publishedSnapshot.Content?.GetById(udi.Guid);
+ IPublishedContent? content = _contentCache.GetById(udi.Guid);
return content != null
? _apiContentBuilder.Build(content)
: null;
}).WhereNotNull().ToArray(),
Constants.UdiEntityType.Media => entityTypeUdis.Select(udi =>
{
- IPublishedContent? media = publishedSnapshot.Media?.GetById(udi.Guid);
+ IPublishedContent? media = _mediaCache.GetById(udi.Guid);
return media != null
? _apiMediaBuilder.Build(media)
: null;
diff --git a/src/Umbraco.Core/PublishedCache/IDatabaseCacheRebuilder.cs b/src/Umbraco.Core/PublishedCache/IDatabaseCacheRebuilder.cs
new file mode 100644
index 0000000000..4eac53dc03
--- /dev/null
+++ b/src/Umbraco.Core/PublishedCache/IDatabaseCacheRebuilder.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Core.PublishedCache;
+
+public interface IDatabaseCacheRebuilder
+{
+ void Rebuild();
+}
diff --git a/src/Umbraco.Core/PublishedCache/IPublishedCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedCache.cs
index e4d8a2311c..2a8809deb6 100644
--- a/src/Umbraco.Core/PublishedCache/IPublishedCache.cs
+++ b/src/Umbraco.Core/PublishedCache/IPublishedCache.cs
@@ -67,7 +67,7 @@ public interface IPublishedCache
/// A culture.
/// The contents.
/// The value of overrides defaults.
- [Obsolete] // FIXME: Remove when replacing nucache
+ [Obsolete("Scheduled for removal, use IDocumentNavigationQueryService instead in v17")]
IEnumerable GetAtRoot(bool preview, string? culture = null);
///
@@ -76,7 +76,7 @@ public interface IPublishedCache
/// A culture.
/// The contents.
/// Considers published or unpublished content depending on defaults.
- [Obsolete] // FIXME: Remove when replacing nucache
+ [Obsolete("Scheduled for removal, use IDocumentNavigationQueryService instead in v17")]
IEnumerable GetAtRoot(string? culture = null);
///
@@ -85,7 +85,7 @@ public interface IPublishedCache
/// A value indicating whether to consider unpublished content.
/// A value indicating whether the cache contains published content.
/// The value of overrides defaults.
- [Obsolete] // FIXME: Remove when replacing nucache
+ [Obsolete("Scheduled for removal in v17")]
bool HasContent(bool preview);
///
@@ -93,7 +93,7 @@ public interface IPublishedCache
///
/// A value indicating whether the cache contains published content.
/// Considers published or unpublished content depending on defaults.
- [Obsolete] // FIXME: Remove when replacing nucache
+ [Obsolete("Scheduled for removal in v17")]
bool HasContent();
///
@@ -118,7 +118,7 @@ public interface IPublishedCache
///
/// The content type.
/// The contents.
- [Obsolete] // FIXME: Remove when replacing nucache
+ [Obsolete]
IEnumerable GetByContentType(IPublishedContentType contentType);
///
diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshot.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshot.cs
deleted file mode 100644
index 43e6291701..0000000000
--- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshot.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using Umbraco.Cms.Core.Cache;
-
-namespace Umbraco.Cms.Core.PublishedCache;
-
-///
-/// Specifies a published snapshot.
-///
-///
-/// A published snapshot is a point-in-time capture of the current state of
-/// everything that is "published".
-///
-public interface IPublishedSnapshot : IDisposable
-{
- ///
- /// Gets the .
- ///
- IPublishedContentCache? Content { get; }
-
- ///
- /// Gets the .
- ///
- IPublishedMediaCache? Media { get; }
-
- ///
- /// Gets the .
- ///
- IPublishedMemberCache? Members { get; }
-
- ///
- /// Gets the .
- ///
- IDomainCache? Domains { get; }
-
- ///
- /// Gets the snapshot-level cache.
- ///
- ///
- /// The snapshot-level cache belongs to this snapshot only.
- ///
- IAppCache? SnapshotCache { get; }
-
- ///
- /// Gets the elements-level cache.
- ///
- ///
- ///
- /// The elements-level cache is shared by all snapshots relying on the same elements,
- /// ie all snapshots built on top of unchanging content / media / etc.
- ///
- ///
- IAppCache? ElementsCache { get; }
-
- ///
- /// Forces the preview mode.
- ///
- /// The forced preview mode.
- /// A callback to execute when reverting to previous preview.
- ///
- ///
- /// Forcing to false means no preview. Forcing to true means 'full' preview if the snapshot is not already
- /// previewing;
- /// otherwise the snapshot keeps previewing according to whatever settings it is using already.
- ///
- /// Stops forcing preview when disposed.
- ///
- IDisposable ForcedPreview(bool preview, Action? callback = null);
-}
diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs
deleted file mode 100644
index 8abc0906ce..0000000000
--- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace Umbraco.Cms.Core.PublishedCache;
-
-///
-/// Provides access to a TryGetPublishedSnapshot bool method that will return true if the "current"
-/// is not null.
-///
-public interface IPublishedSnapshotAccessor
-{
- bool TryGetPublishedSnapshot([NotNullWhen(true)] out IPublishedSnapshot? publishedSnapshot);
-}
diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
deleted file mode 100644
index 8e661aa758..0000000000
--- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
+++ /dev/null
@@ -1,132 +0,0 @@
-using Umbraco.Cms.Core.Cache;
-
-namespace Umbraco.Cms.Core.PublishedCache;
-
-///
-/// Creates and manages instances.
-///
-public interface IPublishedSnapshotService : IDisposable
-{
- /* Various places (such as Node) want to access the XML content, today as an XmlDocument
- * but to migrate to a new cache, they're migrating to an XPathNavigator. Still, they need
- * to find out how to get that navigator.
- *
- * Because a cache such as NuCache is contextual i.e. it has a "snapshot" thing and remains
- * consistent over the snapshot, the navigator has to come from the "current" snapshot.
- *
- * So although everything should be injected... we also need a notion of "the current published
- * snapshot". This is provided by the IPublishedSnapshotAccessor.
- *
- */
-
- ///
- /// Creates a published snapshot.
- ///
- /// A preview token, or null if not previewing.
- /// A published snapshot.
- ///
- /// If is null, the snapshot is not previewing, else it
- /// is previewing, and what is or is not visible in preview depends on the content of the token,
- /// which is not specified and depends on the actual published snapshot service implementation.
- ///
- IPublishedSnapshot CreatePublishedSnapshot(string? previewToken);
-
- ///
- /// Rebuilds internal database caches (but does not reload).
- ///
- ///
- /// If not null will process content for the matching content types, if empty will process all
- /// content
- ///
- ///
- /// If not null will process content for the matching media types, if empty will process all
- /// media
- ///
- ///
- /// If not null will process content for the matching members types, if empty will process all
- /// members
- ///
- ///
- ///
- /// Forces the snapshot service to rebuild its internal database caches. For instance, some caches
- /// may rely on a database table to store pre-serialized version of documents.
- ///
- ///
- /// This does *not* reload the caches. Caches need to be reloaded, for instance via
- /// RefreshAllPublishedSnapshot method.
- ///
- ///
- void Rebuild(
- IReadOnlyCollection? contentTypeIds = null,
- IReadOnlyCollection? mediaTypeIds = null,
- IReadOnlyCollection? memberTypeIds = null);
-
-
- ///
- /// Rebuilds all internal database caches (but does not reload).
- ///
- ///
- ///
- /// Forces the snapshot service to rebuild its internal database caches. For instance, some caches
- /// may rely on a database table to store pre-serialized version of documents.
- ///
- ///
- /// This does *not* reload the caches. Caches need to be reloaded, for instance via
- /// RefreshAllPublishedSnapshot method.
- ///
- ///
- void RebuildAll() => Rebuild(Array.Empty(), Array.Empty(), Array.Empty());
-
- /* An IPublishedCachesService implementation can rely on transaction-level events to update
- * its internal, database-level data, as these events are purely internal. However, it cannot
- * rely on cache refreshers CacheUpdated events to update itself, as these events are external
- * and the order-of-execution of the handlers cannot be guaranteed, which means that some
- * user code may run before Umbraco is finished updating itself. Instead, the cache refreshers
- * explicitly notify the service of changes.
- *
- */
-
- ///
- /// Notifies of content cache refresher changes.
- ///
- /// The changes.
- /// A value indicating whether draft contents have been changed in the cache.
- /// A value indicating whether published contents have been changed in the cache.
- void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged);
-
- ///
- /// Notifies of media cache refresher changes.
- ///
- /// The changes.
- /// A value indicating whether medias have been changed in the cache.
- void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged);
-
- // there is no NotifyChanges for MemberCacheRefresher because we're not caching members.
-
- ///
- /// Notifies of content type refresher changes.
- ///
- /// The changes.
- void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads);
-
- ///
- /// Notifies of data type refresher changes.
- ///
- /// The changes.
- void Notify(DataTypeCacheRefresher.JsonPayload[] payloads);
-
- ///
- /// Notifies of domain refresher changes.
- ///
- /// The changes.
- void Notify(DomainCacheRefresher.JsonPayload[] payloads);
-
- ///
- /// Cleans up unused snapshots
- ///
- Task CollectAsync();
-
- void ResetLocalDb()
- {
- }
-}
diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs
deleted file mode 100644
index 1ae08cc42d..0000000000
--- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Umbraco.Cms.Core.PublishedCache;
-
-///
-/// Returns the currents status for nucache
-///
-public interface IPublishedSnapshotStatus
-{
- ///
- /// Gets the status report as a string
- ///
- string GetStatus();
-}
diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs
index f4e381d3a1..f8bffbba77 100644
--- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs
+++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs
@@ -1,5 +1,8 @@
using System.ComponentModel;
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
+using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PublishedCache.Internal;
@@ -66,13 +69,18 @@ public sealed class InternalPublishedContent : IPublishedContent
public PublishedItemType ItemType => PublishedItemType.Content;
- public IPublishedContent? Parent { get; set; }
+ [Obsolete("Please use TryGetParentKey() on IDocumentNavigationQueryService or IMediaNavigationQueryService instead. Scheduled for removal in V16.")]
+ public IPublishedContent? Parent => this.Parent(StaticServiceProvider.Instance.GetRequiredService(), StaticServiceProvider.Instance.GetRequiredService());
public bool IsDraft(string? culture = null) => false;
public bool IsPublished(string? culture = null) => true;
- public IEnumerable Children { get; set; } = Enumerable.Empty();
+ [Obsolete("Please use TryGetChildrenKeys() on IDocumentNavigationQueryService or IMediaNavigationQueryService instead. Scheduled for removal in V16.")]
+ public IEnumerable Children => this.Children(
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService());
public IEnumerable ChildrenForAllCultures => Children;
@@ -94,7 +102,7 @@ public sealed class InternalPublishedContent : IPublishedContent
IPublishedContent? content = this;
while (content != null && (property == null || property.HasValue() == false))
{
- content = content.Parent;
+ content = content.Parent(StaticServiceProvider.Instance.GetRequiredService(), StaticServiceProvider.Instance.GetRequiredService());
property = content?.GetProperty(alias);
}
diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs
deleted file mode 100644
index 5b57236d4f..0000000000
--- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using System.ComponentModel;
-using Umbraco.Cms.Core.Models.PublishedContent;
-
-namespace Umbraco.Cms.Core.PublishedCache.Internal;
-
-// TODO: Only used in unit tests, needs to be moved to test project
-[EditorBrowsable(EditorBrowsableState.Never)]
-public sealed class InternalPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache
-{
- private readonly Dictionary _content = new();
-
- public InternalPublishedContentCache()
- : base(false)
- {
- }
-
- public Task GetByIdAsync(int id, bool preview = false) => throw new NotImplementedException();
-
- public Task GetByIdAsync(Guid key, bool preview = false) => throw new NotImplementedException();
-
- public Task HasByIdAsync(int id, bool preview = false) => throw new NotImplementedException();
-
- public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string? culture = null) => throw new NotImplementedException();
-
- public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string? culture = null) =>
- throw new NotImplementedException();
-
- public string GetRouteById(bool preview, int contentId, string? culture = null) =>
- throw new NotImplementedException();
-
- public string GetRouteById(int contentId, string? culture = null) => throw new NotImplementedException();
-
- public override IPublishedContent? GetById(bool preview, int contentId) =>
- _content.ContainsKey(contentId) ? _content[contentId] : null;
-
- public override IPublishedContent GetById(bool preview, Guid contentId) => throw new NotImplementedException();
-
- public override IPublishedContent GetById(bool preview, Udi nodeId) => throw new NotSupportedException();
-
- public override bool HasById(bool preview, int contentId) => _content.ContainsKey(contentId);
-
- public override IEnumerable GetAtRoot(bool preview, string? culture = null) =>
- _content.Values.Where(x => x.Parent == null);
-
- public override bool HasContent(bool preview) => _content.Count > 0;
-
- public override IPublishedContentType GetContentType(int id) => throw new NotImplementedException();
-
- public override IPublishedContentType GetContentType(string alias) => throw new NotImplementedException();
-
- public override IPublishedContentType GetContentType(Guid key) => throw new NotImplementedException();
-
- public override IEnumerable GetByContentType(IPublishedContentType contentType) =>
- throw new NotImplementedException();
-
- // public void Add(InternalPublishedContent content) => _content[content.Id] = content.CreateModel(Mock.Of());
- public void Clear() => _content.Clear();
- public Task GetByIdAsync(int id) => throw new NotImplementedException();
-
- public Task GetByKeyAsync(Guid key) => throw new NotImplementedException();
-
- public Task HasByIdAsync(int id) => throw new NotImplementedException();
-}
diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs
deleted file mode 100644
index 015962b5aa..0000000000
--- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.ComponentModel;
-using Umbraco.Cms.Core.Cache;
-
-namespace Umbraco.Cms.Core.PublishedCache.Internal;
-
-// TODO: Only used in unit tests, needs to be moved to test project
-[EditorBrowsable(EditorBrowsableState.Never)]
-public sealed class InternalPublishedSnapshot : IPublishedSnapshot
-{
- public InternalPublishedContentCache InnerContentCache { get; } = new();
-
- public InternalPublishedContentCache InnerMediaCache { get; } = new();
-
- public IPublishedContentCache Content => InnerContentCache;
-
- public IPublishedMediaCache Media => InnerMediaCache;
-
- public IPublishedMemberCache? Members => null;
-
- public IDomainCache? Domains => null;
-
- public IAppCache? SnapshotCache => null;
-
- public IDisposable ForcedPreview(bool forcedPreview, Action? callback = null) =>
- throw new NotImplementedException();
-
- public IAppCache? ElementsCache => null;
-
- public void Dispose()
- {
- }
-
- public void Resync()
- {
- }
-}
diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs
deleted file mode 100644
index 09de76ace5..0000000000
--- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System.ComponentModel;
-using Umbraco.Cms.Core.Cache;
-using Umbraco.Extensions;
-
-namespace Umbraco.Cms.Core.PublishedCache.Internal;
-
-// TODO: Only used in unit tests, needs to be moved to test project
-[EditorBrowsable(EditorBrowsableState.Never)]
-public class InternalPublishedSnapshotService : IPublishedSnapshotService
-{
- private InternalPublishedSnapshot? _previewSnapshot;
- private InternalPublishedSnapshot? _snapshot;
-
- public Task CollectAsync() => Task.CompletedTask;
-
- public IPublishedSnapshot CreatePublishedSnapshot(string? previewToken)
- {
- if (previewToken.IsNullOrWhiteSpace())
- {
- return _snapshot ??= new InternalPublishedSnapshot();
- }
-
- return _previewSnapshot ??= new InternalPublishedSnapshot();
- }
-
- public void Dispose()
- {
- _snapshot?.Dispose();
- _previewSnapshot?.Dispose();
- }
-
- public void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged)
- {
- draftChanged = false;
- publishedChanged = false;
- }
-
- public void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) => anythingChanged = false;
-
- public void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads)
- {
- }
-
- public void Notify(DataTypeCacheRefresher.JsonPayload[] payloads)
- {
- }
-
- public void Notify(DomainCacheRefresher.JsonPayload[] payloads)
- {
- }
-
- public void Rebuild(IReadOnlyCollection? contentTypeIds = null, IReadOnlyCollection? mediaTypeIds = null, IReadOnlyCollection? memberTypeIds = null)
- {
- }
-}
diff --git a/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs b/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs
index 88acd5d29c..2abba65d4a 100644
--- a/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs
+++ b/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs
@@ -64,11 +64,5 @@ public abstract class PublishedCacheBase : IPublishedCache
public abstract IPublishedContentType? GetContentType(Guid key);
- public virtual IEnumerable GetByContentType(IPublishedContentType contentType) =>
-
- // this is probably not super-efficient, but works
- // some cache implementation may want to override it, though
- GetAtRoot()
- .SelectMany(x => x.DescendantsOrSelf(_variationContextAccessor!))
- .Where(x => x.ContentType.Id == contentType.Id);
+ public virtual IEnumerable GetByContentType(IPublishedContentType contentType) => throw new NotImplementedException();
}
diff --git a/src/Umbraco.Core/PublishedCache/PublishedElement.cs b/src/Umbraco.Core/PublishedCache/PublishedElement.cs
index 297a62b589..92d8646539 100644
--- a/src/Umbraco.Core/PublishedCache/PublishedElement.cs
+++ b/src/Umbraco.Core/PublishedCache/PublishedElement.cs
@@ -18,7 +18,7 @@ public class PublishedElement : IPublishedElement
// initializes a new instance of the PublishedElement class
// within the context of a published snapshot service (eg a published content property value)
- public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary? values, bool previewing, PropertyCacheLevel referenceCacheLevel, IPublishedSnapshotAccessor? publishedSnapshotAccessor)
+ public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary? values, bool previewing, PropertyCacheLevel referenceCacheLevel, ICacheManager? cacheManager)
{
if (key == Guid.Empty)
{
@@ -30,13 +30,6 @@ public class PublishedElement : IPublishedElement
throw new ArgumentNullException(nameof(values));
}
- if (referenceCacheLevel != PropertyCacheLevel.None && publishedSnapshotAccessor == null)
- {
- throw new ArgumentNullException(
- "A published snapshot accessor is required when referenceCacheLevel != None.",
- nameof(publishedSnapshotAccessor));
- }
-
ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType));
Key = key;
@@ -47,7 +40,7 @@ public class PublishedElement : IPublishedElement
.Select(propertyType =>
{
values.TryGetValue(propertyType.Alias, out var value);
- return (IPublishedProperty)new PublishedElementPropertyBase(propertyType, this, previewing, referenceCacheLevel, value, publishedSnapshotAccessor);
+ return (IPublishedProperty)new PublishedElementPropertyBase(propertyType, this, previewing, referenceCacheLevel,cacheManager, value);
})
.ToArray()
?? new IPublishedProperty[0];
diff --git a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs
index 53e8156538..0452cf0b03 100644
--- a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs
+++ b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs
@@ -15,10 +15,10 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase
// so making it configurable.
private const bool FullCacheWhenPreviewing = true;
private readonly object _locko = new();
- private readonly IPublishedSnapshotAccessor? _publishedSnapshotAccessor;
private readonly object? _sourceValue;
protected readonly bool IsMember;
protected readonly bool IsPreviewing;
+ private readonly ICacheManager? _cacheManager;
private CacheValues? _cacheValues;
private bool _interInitialized;
@@ -30,14 +30,14 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase
IPublishedElement element,
bool previewing,
PropertyCacheLevel referenceCacheLevel,
- object? sourceValue = null,
- IPublishedSnapshotAccessor? publishedSnapshotAccessor = null)
+ ICacheManager? cacheManager,
+ object? sourceValue = null)
: base(propertyType, referenceCacheLevel)
{
_sourceValue = sourceValue;
- _publishedSnapshotAccessor = publishedSnapshotAccessor;
Element = element;
IsPreviewing = previewing;
+ _cacheManager = cacheManager;
IsMember = propertyType.ContentType?.ItemType == PublishedItemType.Member;
}
@@ -118,33 +118,13 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase
}
}
- private IAppCache? GetSnapshotCache()
- {
- // cache within the snapshot cache, unless previewing, then use the snapshot or
- // elements cache (if we don't want to pollute the elements cache with short-lived
- // data) depending on settings
- // for members, always cache in the snapshot cache - never pollute elements cache
- if (_publishedSnapshotAccessor is null)
- {
- return null;
- }
-
- if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot))
- {
- return null;
- }
-
- return (IsPreviewing == false || FullCacheWhenPreviewing) && IsMember == false
- ? publishedSnapshot!.ElementsCache
- : publishedSnapshot!.SnapshotCache;
- }
-
private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)
{
CacheValues cacheValues;
switch (cacheLevel)
{
case PropertyCacheLevel.None:
+ case PropertyCacheLevel.Snapshot:
// never cache anything
cacheValues = new CacheValues();
break;
@@ -153,17 +133,7 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase
cacheValues = _cacheValues ??= new CacheValues();
break;
case PropertyCacheLevel.Elements:
- // cache within the elements cache, depending...
- IAppCache? snapshotCache = GetSnapshotCache();
- cacheValues = (CacheValues?)snapshotCache?.Get(ValuesCacheKey, () => new CacheValues()) ??
- new CacheValues();
- break;
- case PropertyCacheLevel.Snapshot:
- IPublishedSnapshot? publishedSnapshot = _publishedSnapshotAccessor?.GetRequiredPublishedSnapshot();
-
- // cache within the snapshot cache
- IAppCache? facadeCache = publishedSnapshot?.SnapshotCache;
- cacheValues = (CacheValues?)facadeCache?.Get(ValuesCacheKey, () => new CacheValues()) ??
+ cacheValues = (CacheValues?)_cacheManager?.ElementsCache.Get(ValuesCacheKey, () => new CacheValues()) ??
new CacheValues();
break;
default:
diff --git a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs
deleted file mode 100644
index 91e32e6db4..0000000000
--- a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using Umbraco.Cms.Core.Web;
-
-namespace Umbraco.Cms.Core.PublishedCache;
-
-// TODO: This is a mess. This is a circular reference:
-// IPublishedSnapshotAccessor -> PublishedSnapshotService -> UmbracoContext -> PublishedSnapshotService -> IPublishedSnapshotAccessor
-// Injecting IPublishedSnapshotAccessor into PublishedSnapshotService seems pretty strange
-// The underlying reason for this mess is because IPublishedContent is both a service and a model.
-// Until that is fixed, IPublishedContent will need to have a IPublishedSnapshotAccessor
-public class UmbracoContextPublishedSnapshotAccessor : IPublishedSnapshotAccessor
-{
- private readonly IUmbracoContextAccessor _umbracoContextAccessor;
-
- public UmbracoContextPublishedSnapshotAccessor(IUmbracoContextAccessor umbracoContextAccessor) =>
- _umbracoContextAccessor = umbracoContextAccessor;
-
- public IPublishedSnapshot? PublishedSnapshot
- {
- get
- {
- if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext))
- {
- return null;
- }
-
- return umbracoContext?.PublishedSnapshot;
- }
-
- set => throw new NotSupportedException(); // not ok to set
- }
-
- public bool TryGetPublishedSnapshot([NotNullWhen(true)] out IPublishedSnapshot? publishedSnapshot)
- {
- if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext))
- {
- publishedSnapshot = null;
- return false;
- }
-
- publishedSnapshot = umbracoContext?.PublishedSnapshot;
-
- return publishedSnapshot is not null;
- }
-}
diff --git a/src/Umbraco.Core/Routing/AliasUrlProvider.cs b/src/Umbraco.Core/Routing/AliasUrlProvider.cs
index 65d9387e2e..59e9e1d381 100644
--- a/src/Umbraco.Core/Routing/AliasUrlProvider.cs
+++ b/src/Umbraco.Core/Routing/AliasUrlProvider.cs
@@ -1,6 +1,10 @@
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models.PublishedContent;
+using Umbraco.Cms.Core.PublishedCache;
+using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Core.Web;
using Umbraco.Extensions;
@@ -14,6 +18,8 @@ public class AliasUrlProvider : IUrlProvider
private readonly IPublishedValueFallback _publishedValueFallback;
private readonly ISiteDomainMapper _siteDomainMapper;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
+ private readonly IPublishedContentCache _contentCache;
+ private readonly IDocumentNavigationQueryService _navigationQueryService;
private readonly UriUtility _uriUtility;
private RequestHandlerSettings _requestConfig;
@@ -22,17 +28,39 @@ public class AliasUrlProvider : IUrlProvider
ISiteDomainMapper siteDomainMapper,
UriUtility uriUtility,
IPublishedValueFallback publishedValueFallback,
- IUmbracoContextAccessor umbracoContextAccessor)
+ IUmbracoContextAccessor umbracoContextAccessor,
+ IPublishedContentCache contentCache,
+ IDocumentNavigationQueryService navigationQueryService)
{
_requestConfig = requestConfig.CurrentValue;
_siteDomainMapper = siteDomainMapper;
_uriUtility = uriUtility;
_publishedValueFallback = publishedValueFallback;
_umbracoContextAccessor = umbracoContextAccessor;
+ _contentCache = contentCache;
+ _navigationQueryService = navigationQueryService;
requestConfig.OnChange(x => _requestConfig = x);
}
+ [Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
+ public AliasUrlProvider(
+ IOptionsMonitor requestConfig,
+ ISiteDomainMapper siteDomainMapper,
+ UriUtility uriUtility,
+ IPublishedValueFallback publishedValueFallback,
+ IUmbracoContextAccessor umbracoContextAccessor)
+ : this(
+ requestConfig,
+ siteDomainMapper,
+ uriUtility,
+ publishedValueFallback,
+ umbracoContextAccessor,
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService())
+ {
+ }
+
// note - at the moment we seem to accept pretty much anything as an alias
// without any form of validation ... could even prob. kill the XPath ...
// ok, this is somewhat experimental and is NOT enabled by default
@@ -74,16 +102,16 @@ public class AliasUrlProvider : IUrlProvider
// look for domains, walking up the tree
IPublishedContent? n = node;
- IEnumerable? domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false);
+ IEnumerable? domainUris = DomainUtilities.DomainsForNode(umbracoContext.Domains, _siteDomainMapper, n.Id, current, false);
// n is null at root
while (domainUris == null && n != null)
{
// move to parent node
- n = n.Parent;
+ n = n.Parent(_contentCache, _navigationQueryService);
domainUris = n == null
? null
- : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false);
+ : DomainUtilities.DomainsForNode(umbracoContext.Domains, _siteDomainMapper, n.Id, current, false);
}
// determine whether the alias property varies
diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs
index 3a04c2cb5b..f592fb0a0f 100644
--- a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs
+++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
+using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Core.Web;
using Umbraco.Extensions;
@@ -21,6 +22,8 @@ public class ContentFinderByUrlAlias : IContentFinder
private readonly ILogger _logger;
private readonly IPublishedValueFallback _publishedValueFallback;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
+ private readonly IPublishedContentCache _contentCache;
+ private readonly IDocumentNavigationQueryService _documentNavigationQueryService;
private readonly IVariationContextAccessor _variationContextAccessor;
///