From 82beeb5bcfc78a8f3307df6421efb3493a24c284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20Hoskam?= Date: Wed, 30 Jul 2025 15:27:14 +0200 Subject: [PATCH 1/5] Fixes #19654 Adds the propertyAlias to the VariationContext so that products implementing the GetSegment method are aware which propertyAlias it's being called for --- .../PublishedContent/PublishedValueFallback.cs | 8 ++++---- .../Models/PublishedContent/VariationContext.cs | 3 ++- .../VariationContextAccessorExtensions.cs | 15 +++++++++------ src/Umbraco.PublishedCache.NuCache/Property.cs | 10 +++++----- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs index 0524ee98a91f..e593bd75d7b6 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs @@ -30,7 +30,7 @@ public bool TryGetValue(IPublishedProperty property, string? culture, string? se /// public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value) { - _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment, property.Alias); foreach (var f in fallback) { @@ -78,7 +78,7 @@ public bool TryGetValue(IPublishedElement content, string alias, string? cult return false; } - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment, alias); foreach (var f in fallback) { @@ -124,7 +124,7 @@ public virtual bool TryGetValue(IPublishedContent content, string alias, stri IPublishedPropertyType? propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) { - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment, alias); noValueProperty = content.GetProperty(alias); } @@ -195,7 +195,7 @@ private bool TryGetValueWithAncestorsFallback(IPublishedContent? content, str { culture = null; segment = null; - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment, alias); } property = content?.GetProperty(alias); diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index 92326ae35955..179ef075871c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -28,6 +28,7 @@ public VariationContext(string? culture = null, string? segment = null) /// Gets the segment for the content item /// /// + /// /// - public virtual string GetSegment(int contentId) => Segment; + public virtual string GetSegment(int contentId, string? propertyAlias = null) => Segment; } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index e8f6e3bdc1a7..56bf5d462e80 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -12,23 +12,26 @@ public static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string? culture, - ref string? segment) - => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment); + ref string? segment, + string? propertyAlias) + => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment, propertyAlias); public static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int contentId, ref string? culture, - ref string? segment) - => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment); + ref string? segment, + string? propertyAlias) + => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment, propertyAlias); private static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int? contentId, ref string? culture, - ref string? segment) + ref string? segment, + string? propertyAlias) { if (culture != null && segment != null) { @@ -48,7 +51,7 @@ private static void ContextualizeVariation( { segment = contentId == null ? publishedVariationContext?.Segment - : publishedVariationContext?.GetSegment(contentId.Value); + : publishedVariationContext?.GetSegment(contentId.Value, propertyAlias); } else { diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index 41532d4944c1..14bb9cea6f14 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -114,7 +114,7 @@ public Property(Property origin, PublishedContent content) // determines whether a property has value public override bool HasValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment, PropertyType.Alias); var value = GetSourceValue(culture, segment); var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source); @@ -148,7 +148,7 @@ public override bool HasValue(string? culture = null, string? segment = null) public override object? GetSourceValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_sourceVariations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_sourceVariations, _content.Id, ref culture, ref segment, PropertyType.Alias); // source values are tightly bound to the property/schema culture and segment configurations, so we need to // sanitize the contextualized culture/segment states before using them to access the source values. @@ -262,7 +262,7 @@ private CacheValues GetCacheValues(IAppCache? cache) public override object? GetValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment, PropertyType.Alias); object? value; CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); @@ -285,7 +285,7 @@ private CacheValues GetCacheValues(IAppCache? cache) [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? GetXPathValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment, PropertyType.Alias); CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); @@ -304,7 +304,7 @@ private CacheValues GetCacheValues(IAppCache? cache) public override object? GetDeliveryApiValue(bool expanding, string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment, PropertyType.Alias); object? value; CacheValue cacheValues = GetCacheValues(expanding ? PropertyType.DeliveryApiCacheLevelForExpansion : PropertyType.DeliveryApiCacheLevel).For(culture, segment); From a2f63f357f0513cd352318ce750f9ab0dddf930a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20Hoskam?= Date: Wed, 30 Jul 2025 15:48:02 +0200 Subject: [PATCH 2/5] Re-implement original variation context for backwards compatibility --- .../Models/PublishedContent/VariationContext.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index 179ef075871c..6beed2e7905b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -24,6 +24,13 @@ public VariationContext(string? culture = null, string? segment = null) /// public string Segment { get; } + /// + /// Gets the segment for the content item + /// + /// + /// + public virtual string GetSegment(int contentId) => Segment; + /// /// Gets the segment for the content item /// From 4ff5594d4cb296008200352d2bf125e9de59972e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20Hoskam?= Date: Wed, 30 Jul 2025 15:54:14 +0200 Subject: [PATCH 3/5] Fixes hidden overload method Ensures the `GetSegment` method overload is not hidden when a null `propertyAlias` is passed. --- .../Models/PublishedContent/VariationContext.cs | 2 +- .../VariationContextAccessorExtensions.cs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index 6beed2e7905b..68bd5be1cda6 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -37,5 +37,5 @@ public VariationContext(string? culture = null, string? segment = null) /// /// /// - public virtual string GetSegment(int contentId, string? propertyAlias = null) => Segment; + public virtual string GetSegment(int contentId, string propertyAlias) => Segment; } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index 56bf5d462e80..fb44ee0049af 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -49,9 +49,16 @@ private static void ContextualizeVariation( { if (variations.VariesBySegment()) { - segment = contentId == null - ? publishedVariationContext?.Segment - : publishedVariationContext?.GetSegment(contentId.Value, propertyAlias); + if (contentId == null) + { + segment = publishedVariationContext?.Segment; + } + else + { + segment = propertyAlias == null ? + publishedVariationContext?.GetSegment(contentId.Value) : + publishedVariationContext?.GetSegment(contentId.Value, propertyAlias); + } } else { From 00b29db76514ed0adcac2e775f0b29ec0cdfcca7 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 1 Aug 2025 13:57:51 +0200 Subject: [PATCH 4/5] Resolve backward compatibility issues. --- .../PublishedValueFallback.cs | 8 ++-- .../PublishedContent/VariationContext.cs | 6 +-- .../VariationContextAccessorExtensions.cs | 38 +++++++++++++------ .../Property.cs | 10 ++--- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs index e593bd75d7b6..c1601e8a5289 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs @@ -30,7 +30,7 @@ public bool TryGetValue(IPublishedProperty property, string? culture, string? se /// public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value) { - _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment, property.Alias); + _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, property.Alias, ref culture, ref segment); foreach (var f in fallback) { @@ -78,7 +78,7 @@ public bool TryGetValue(IPublishedElement content, string alias, string? cult return false; } - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment, alias); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, alias, ref culture, ref segment); foreach (var f in fallback) { @@ -124,7 +124,7 @@ public virtual bool TryGetValue(IPublishedContent content, string alias, stri IPublishedPropertyType? propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) { - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment, alias); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, alias, ref culture, ref segment); noValueProperty = content.GetProperty(alias); } @@ -195,7 +195,7 @@ private bool TryGetValueWithAncestorsFallback(IPublishedContent? content, str { culture = null; segment = null; - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment, alias); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, alias, ref culture, ref segment); } property = content?.GetProperty(alias); diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index 68bd5be1cda6..ced6e2b599b6 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -25,17 +25,15 @@ public VariationContext(string? culture = null, string? segment = null) public string Segment { get; } /// - /// Gets the segment for the content item + /// Gets the segment for the content item. /// /// - /// public virtual string GetSegment(int contentId) => Segment; /// - /// Gets the segment for the content item + /// Gets the segment for the content item. /// /// /// - /// public virtual string GetSegment(int contentId, string propertyAlias) => Segment; } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index fb44ee0049af..566d5e45af08 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -8,30 +8,47 @@ namespace Umbraco.Extensions; public static class VariationContextAccessorExtensions { + [Obsolete("Please use the method overload that accepts all parameters. Scheduled for removal in Umbraco 18.")] public static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string? culture, - ref string? segment, - string? propertyAlias) - => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment, propertyAlias); + ref string? segment) + => variationContextAccessor.ContextualizeVariation(variations, null, null, ref culture, ref segment); + public static void ContextualizeVariation( + this IVariationContextAccessor variationContextAccessor, + ContentVariation variations, + string? propertyAlias, + ref string? culture, + ref string? segment) + => variationContextAccessor.ContextualizeVariation(variations, null, propertyAlias, ref culture, ref segment); + + [Obsolete("Please use the method overload that accepts all parameters. Scheduled for removal in Umbraco 18.")] public static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int contentId, ref string? culture, - ref string? segment, - string? propertyAlias) - => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment, propertyAlias); + ref string? segment) + => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, null, ref culture, ref segment); + + public static void ContextualizeVariation( + this IVariationContextAccessor variationContextAccessor, + ContentVariation variations, + int contentId, + string? propertyAlias, + ref string? culture, + ref string? segment) + => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, propertyAlias, ref culture, ref segment); private static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int? contentId, + string? propertyAlias, ref string? culture, - ref string? segment, - string? propertyAlias) + ref string? segment) { if (culture != null && segment != null) { @@ -40,10 +57,7 @@ private static void ContextualizeVariation( // use context values VariationContext? publishedVariationContext = variationContextAccessor?.VariationContext; - if (culture == null) - { - culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : string.Empty; - } + culture ??= variations.VariesByCulture() ? publishedVariationContext?.Culture : string.Empty; if (segment == null) { diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index 14bb9cea6f14..2d5635a12818 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -114,7 +114,7 @@ public Property(Property origin, PublishedContent content) // determines whether a property has value public override bool HasValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment, PropertyType.Alias); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, PropertyType.Alias, ref culture, ref segment); var value = GetSourceValue(culture, segment); var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source); @@ -148,7 +148,7 @@ public override bool HasValue(string? culture = null, string? segment = null) public override object? GetSourceValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_sourceVariations, _content.Id, ref culture, ref segment, PropertyType.Alias); + _content.VariationContextAccessor.ContextualizeVariation(_sourceVariations, _content.Id, PropertyType.Alias, ref culture, ref segment); // source values are tightly bound to the property/schema culture and segment configurations, so we need to // sanitize the contextualized culture/segment states before using them to access the source values. @@ -262,7 +262,7 @@ private CacheValues GetCacheValues(IAppCache? cache) public override object? GetValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment, PropertyType.Alias); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, PropertyType.Alias, ref culture, ref segment); object? value; CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); @@ -285,7 +285,7 @@ private CacheValues GetCacheValues(IAppCache? cache) [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? GetXPathValue(string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment, PropertyType.Alias); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, PropertyType.Alias, ref culture, ref segment); CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); @@ -304,7 +304,7 @@ private CacheValues GetCacheValues(IAppCache? cache) public override object? GetDeliveryApiValue(bool expanding, string? culture = null, string? segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment, PropertyType.Alias); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, PropertyType.Alias, ref culture, ref segment); object? value; CacheValue cacheValues = GetCacheValues(expanding ? PropertyType.DeliveryApiCacheLevelForExpansion : PropertyType.DeliveryApiCacheLevel).For(culture, segment); From b6af4fb8e0db2246e335a1c1dbcbca791de06004 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Fri, 1 Aug 2025 14:27:14 +0200 Subject: [PATCH 5/5] Improved comments. --- .../Models/PublishedContent/VariationContext.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index ced6e2b599b6..395e0f9c203e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -27,13 +27,13 @@ public VariationContext(string? culture = null, string? segment = null) /// /// Gets the segment for the content item. /// - /// + /// The content Id. public virtual string GetSegment(int contentId) => Segment; /// - /// Gets the segment for the content item. + /// Gets the segment for the content item and property alias. /// - /// - /// + /// The content Id. + /// The property alias. public virtual string GetSegment(int contentId, string propertyAlias) => Segment; }