Another fallback iteration

This commit is contained in:
Stephan
2018-10-03 10:31:35 +02:00
parent 8a29745b98
commit e44ed00c97
13 changed files with 341 additions and 214 deletions
-48
View File
@@ -1,48 +0,0 @@
namespace Umbraco.Core
{
public static partial class Constants
{
/// <summary>
/// Defines content retrieval related constants
/// </summary>
public static class Content
{
/// <summary>
/// Defines core supported content fall-back options when retrieving content property values.
/// Defined as constants rather than enum to allow solution or package defined fall-back methods.
/// </summary>
public static class ValueFallback
{
/// <summary>
/// No fallback at all.
/// </summary>
public const int None = -1;
/// <summary>
/// Default fallback.
/// </summary>
public const int Default = 0;
/// <summary>
/// Recurse up the tree.
/// </summary>
public const int Recurse = 1;
/// <summary>
/// Fallback to other languages.
/// </summary>
public const int Language = 2;
/// <summary>
/// Recurse up the tree. If content not found, fallback to other languages.
/// </summary>
public const int RecurseThenLanguage = 3;
/// <summary>
/// Fallback to other languages. If content not found, recurse up the tree.
/// </summary>
public const int LanguageThenRecurse = 4;
}
}
}
}
@@ -0,0 +1,75 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Manages the built-in fallback policies.
/// </summary>
public struct Fallback : IEnumerable<int>
{
private readonly int[] _values;
/// <summary>
/// Initializes a new instance of the <see cref="Fallback"/> struct with values.
/// </summary>
private Fallback(int[] values)
{
_values = values;
}
/// <summary>
/// Gets an ordered set of fallback policies.
/// </summary>
/// <param name="values"></param>
public static Fallback To(params int[] values) => new Fallback(values);
/// <summary>
/// Do not fallback.
/// </summary>
public const int None = 0;
/// <summary>
/// Fallback to default value.
/// </summary>
public const int DefaultValue = 1;
/// <summary>
/// Gets the fallback to default value policy.
/// </summary>
public static Fallback ToDefaultValue => new Fallback(new[] { DefaultValue });
/// <summary>
/// Fallback to other languages.
/// </summary>
public const int Language = 2;
/// <summary>
/// Gets the fallback to language policy.
/// </summary>
public static Fallback ToLanguage => new Fallback(new[] { Language });
/// <summary>
/// Fallback to tree ancestors.
/// </summary>
public const int Ancestors = 3;
/// <summary>
/// Gets the fallback to tree ancestors policy.
/// </summary>
public static Fallback ToAncestors => new Fallback(new[] { Ancestors });
/// <inheritdoc />
public IEnumerator<int> GetEnumerator()
{
return ((IEnumerable<int>)_values ?? Array.Empty<int>()).GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -7,16 +7,6 @@
{
// fixme discussions & challenges
//
// - what's with visitedLanguage? should be internal to fallback implementation
// so that should be the case now, with latest changes
//
// - should be as simple as
// model.Value("price", fallback: ValueFallback.Language);
// model.Value("name", fallback: ValueFallback.Recurse);
//
// so chaining things through an array of ints is not... convenient
// it feels like ppl could have ValueFallback.LanguageAndRecurse or something?
//
// - the fallback: parameter value must be open, so about anything can be passed to the IPublishedValueFallback
// we have it now, it's an integer + constants, cool
//
@@ -24,14 +14,6 @@
// not! the default value of the fallback: parameter is 'default', not 'none', and if people
// want to implement a different default behavior, they have to override the fallback provider
//
// - currently, no policies on IPublishedProperty nor IPublishedElement, but some may apply (language)
// todo: implement
//
// - general defaultValue discussion:
// when HasValue is false, the converter may return something, eg an empty enumerable, even though
// defaultValue is null, so should we respect defaultValue only when it is not 'default'?
// todo: when defaultValue==default, and HasValue is false, still return GetValue to ensure this
//
// - (and...)
// ModelsBuilder model.Value(x => x.Price, ...) extensions need to be adjusted too
//
@@ -40,14 +22,15 @@
// OTOH we need to implement the readonly thing for languages
/// <summary>
/// Gets a fallback value for a property.
/// Tries to get a fallback value for a property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="fallback">Integer value defining method to use for fallback when content not found</param>
/// <returns>A fallback value, or null.</returns>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever property.Value(culture, segment, defaultValue) is called, and
/// property.HasValue(culture, segment) is false.</para>
@@ -55,18 +38,19 @@
/// <para>At property level, property.GetValue() does *not* implement fallback, and one has to
/// get property.Value() or property.Value{T}() to trigger fallback.</para>
/// </remarks>
object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue, int fallback);
bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value);
/// <summary>
/// Gets a fallback value for a property.
/// Tries to get a fallback value for a property.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="property">The property.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="fallback">Integer value defining method to use for fallback when content not found</param>
/// <returns>A fallback value, or null.</returns>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever property.Value{T}(culture, segment, defaultValue) is called, and
/// property.HasValue(culture, segment) is false.</para>
@@ -74,76 +58,78 @@
/// <para>At property level, property.GetValue() does *not* implement fallback, and one has to
/// get property.Value() or property.Value{T}() to trigger fallback.</para>
/// </remarks>
T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue, int fallback);
bool TryGetValue<T>(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value);
/// <summary>
/// Gets a fallback value for a published element property.
/// Tries to get a fallback value for a published element property.
/// </summary>
/// <param name="content">The published element.</param>
/// <param name="alias">The property alias.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="fallback">Integer value defining method to use for fallback when content not found</param>
/// <returns>A fallback value, or null.</returns>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever getting the property value for the specified alias, culture and
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
/// <para>It can only fallback at element level (no recurse).</para>
/// </remarks>
object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue, int fallback);
bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value);
/// <summary>
/// Gets a fallback value for a published element property.
/// Tries to get a fallback value for a published element property.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="content">The published element.</param>
/// <param name="alias">The property alias.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="fallback">Integer value defining method to use for fallback when content not found</param>
/// <returns>A fallback value, or null.</returns>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever getting the property value for the specified alias, culture and
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
/// <para>It can only fallback at element level (no recurse).</para>
/// </remarks>
T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue, int fallback);
bool TryGetValue<T>(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value);
/// <summary>
/// Gets a fallback value for a published content property.
/// Tries to get a fallback value for a published content property.
/// </summary>
/// <param name="content">The published element.</param>
/// <param name="alias">The property alias.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="fallback">Integer value defining method to use for fallback when content not found</param>
/// <returns>A fallback value, or null.</returns>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever getting the property value for the specified alias, culture and
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
/// fixme explain & document priority + merge w/recurse?
/// </remarks>
object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, int fallback);
bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value);
/// <summary>
/// Gets a fallback value for a published content property.
/// Tries to get a fallback value for a published content property.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="content">The published element.</param>
/// <param name="alias">The property alias.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="fallback">Integer value defining method to use for fallback when content not found</param>
/// <returns>A fallback value, or null.</returns>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever getting the property value for the specified alias, culture and
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
/// fixme explain & document priority + merge w/recurse?
/// </remarks>
T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, int fallback);
bool TryGetValue<T>(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value);
}
}
@@ -9,21 +9,45 @@
public class NoopPublishedValueFallback : IPublishedValueFallback
{
/// <inheritdoc />
public object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue, int fallback) => defaultValue;
public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value)
{
value = default;
return false;
}
/// <inheritdoc />
public T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue, int fallback) => defaultValue;
public bool TryGetValue<T>(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value)
{
value = default;
return false;
}
/// <inheritdoc />
public object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue, int fallback) => defaultValue;
public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
{
value = default;
return false;
}
/// <inheritdoc />
public T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue, int fallback) => defaultValue;
public bool TryGetValue<T>(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
{
value = default;
return false;
}
/// <inheritdoc />
public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, int fallback) => defaultValue;
public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
{
value = default;
return false;
}
/// <inheritdoc />
public T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, int fallback) => defaultValue;
public bool TryGetValue<T>(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
{
value = default;
return false;
}
}
}
+1 -1
View File
@@ -299,7 +299,6 @@
<Compile Include="Configuration\UmbracoSettings\UserProviderElement.cs" />
<Compile Include="Configuration\UmbracoSettings\WebRoutingElement.cs" />
<Compile Include="Configuration\UmbracoVersion.cs" />
<Compile Include="Constants-Content.cs" />
<Compile Include="Constants-Applications.cs" />
<Compile Include="Constants-Conventions.cs" />
<Compile Include="Constants-DatabaseProviders.cs" />
@@ -393,6 +392,7 @@
<Compile Include="Models\PathValidationExtensions.cs" />
<Compile Include="Models\Entities\TreeEntityBase.cs" />
<Compile Include="Models\PropertyTagsExtensions.cs" />
<Compile Include="Models\PublishedContent\Fallback.cs" />
<Compile Include="Models\PublishedContent\NoopPublishedValueFallback.cs" />
<Compile Include="Models\PublishedContent\PublishedCultureInfos.cs" />
<Compile Include="Models\PublishedContent\IVariationContextAccessor.cs" />
@@ -328,16 +328,16 @@ namespace Umbraco.Tests.Persistence.Repositories
// Add language to delete as a fall-back language to another one
var repository = CreateRepository(provider);
var languageToFallbackFrom = repository.Get(5);
languageToFallbackFrom.FallbackLanguageId = 1;
languageToFallbackFrom.FallbackLanguageId = 2; // fall back to #2 (something we can delete)
repository.Save(languageToFallbackFrom);
// Act
var languageToDelete = repository.Get(1);
// delete #2
var languageToDelete = repository.Get(2);
repository.Delete(languageToDelete);
var exists = repository.Exists(1);
var exists = repository.Exists(2);
// Assert
// has been deleted
Assert.That(exists, Is.False);
}
}
@@ -369,6 +369,8 @@ namespace Umbraco.Tests.Persistence.Repositories
private void CreateTestData()
{
//Id 1 is en-US - when Umbraco is installed
var languageDK = new Language("da-DK") { CultureName = "da-DK" };
ServiceContext.LocalizationService.Save(languageDK);//Id 2
@@ -2,11 +2,13 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Tests.Testing;
using Umbraco.Web;
@@ -63,6 +65,8 @@ namespace Umbraco.Tests.PublishedContent
var props = new[]
{
factory.CreatePropertyType("prop1", 1),
factory.CreatePropertyType("welcomeText", 1),
factory.CreatePropertyType("welcomeText2", 1),
};
var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty<string>(), props);
@@ -168,7 +172,7 @@ namespace Umbraco.Tests.PublishedContent
public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback()
{
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
var value = content.Value("welcomeText", "es", fallback: Core.Constants.Content.ValueFallback.Language);
var value = content.Value("welcomeText", "es", fallback: Fallback.ToLanguage);
Assert.AreEqual("Welcome", value);
}
@@ -176,7 +180,7 @@ namespace Umbraco.Tests.PublishedContent
public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Over_Two_Levels()
{
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
var value = content.Value("welcomeText", "it", fallback: Core.Constants.Content.ValueFallback.Language);
var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors));
Assert.AreEqual("Welcome", value);
}
@@ -184,7 +188,7 @@ namespace Umbraco.Tests.PublishedContent
public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops()
{
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
var value = content.Value("welcomeText", "no", fallback: Core.Constants.Content.ValueFallback.Language);
var value = content.Value("welcomeText", "no", fallback: Fallback.ToLanguage);
Assert.IsNull(value);
}
@@ -200,7 +204,7 @@ namespace Umbraco.Tests.PublishedContent
public void Can_Get_Content_Recursively()
{
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
var value = content.Value("welcomeText2", fallback: Core.Constants.Content.ValueFallback.Recurse);
var value = content.Value("welcomeText2", fallback: Fallback.ToAncestors);
Assert.AreEqual("Welcome", value);
}
@@ -208,7 +212,7 @@ namespace Umbraco.Tests.PublishedContent
public void Can_Get_Content_With_Recursive_Priority()
{
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
var value = content.Value("welcomeText", "nl", fallback: Core.Constants.Content.ValueFallback.RecurseThenLanguage);
var value = content.Value("welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language));
// No Dutch value is directly assigned. Check has fallen back to Dutch value from parent.
Assert.AreEqual("Welkom", value);
@@ -218,7 +222,7 @@ namespace Umbraco.Tests.PublishedContent
public void Can_Get_Content_With_Fallback_Language_Priority()
{
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
var value = content.Value("welcomeText", "nl", fallback: Core.Constants.Content.ValueFallback.LanguageThenRecurse);
var value = content.Value("welcomeText", "nl", fallback: Fallback.ToLanguage);
// No Dutch value is directly assigned. Check has fallen back to English value from language variant.
Assert.AreEqual("Welcome", value);
@@ -228,7 +232,48 @@ namespace Umbraco.Tests.PublishedContent
public void Throws_For_Non_Supported_Fallback()
{
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
Assert.Throws<NotSupportedException>(() => content.Value("welcomeText", "nl", fallback: 999));
Assert.Throws<NotSupportedException>(() => content.Value("welcomeText", "nl", fallback: Fallback.To(999)));
}
[Test]
public void Can_Fallback_To_Default_Value()
{
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
// no Dutch value is assigned, so getting null
var value = content.Value("welcomeText", "nl");
Assert.IsNull(value);
// even if we 'just' provide a default value
value = content.Value("welcomeText", "nl", defaultValue: "woop");
Assert.IsNull(value);
// but it works with proper fallback settings
value = content.Value("welcomeText", "nl", fallback: Fallback.ToDefaultValue, defaultValue: "woop");
Assert.AreEqual("woop", value);
}
[Test]
public void Can_Have_Custom_Default_Value()
{
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First().Children.First();
// hack the value, pretend the converter would return something
var prop = content.GetProperty("welcomeText") as SolidPublishedPropertyWithLanguageVariants;
Assert.IsNotNull(prop);
prop.SetValue("nl", "nope"); // HasValue false but getting value returns this
// there is an EN value
var value = content.Value("welcomeText", "en-US");
Assert.AreEqual("Welcome", value);
// there is no NL value and we get the 'converted' value
value = content.Value("welcomeText", "nl");
Assert.AreEqual("nope", value);
// but it works with proper fallback settings
value = content.Value("welcomeText", "nl", fallback: Fallback.ToDefaultValue, defaultValue: "woop");
Assert.AreEqual("woop", value);
}
}
}
@@ -341,8 +341,8 @@ namespace Umbraco.Tests.PublishedContent
public void Get_Property_Value_Recursive()
{
var doc = GetNode(1174);
var rVal = doc.Value("testRecursive", fallback: Constants.Content.ValueFallback.Recurse);
var nullVal = doc.Value("DoNotFindThis", fallback: Constants.Content.ValueFallback.Recurse);
var rVal = doc.Value("testRecursive", fallback: Fallback.ToAncestors);
var nullVal = doc.Value("DoNotFindThis", fallback: Fallback.ToAncestors);
Assert.AreEqual("This is the recursive val", rVal);
Assert.AreEqual(null, nullVal);
}
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using ValueFallback = Umbraco.Core.Constants.Content.ValueFallback;
namespace Umbraco.Web.Models.PublishedContent
{
@@ -24,82 +23,110 @@ namespace Umbraco.Web.Models.PublishedContent
}
/// <inheritdoc />
public object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue, int fallback)
public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value)
{
return GetValue<object>(property, culture, segment, defaultValue, fallback);
return TryGetValue<object>(property, culture, segment, fallback, defaultValue, out value);
}
/// <inheritdoc />
public T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue, int fallback)
public bool TryGetValue<T>(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value)
{
switch (fallback)
foreach (var f in fallback)
{
case ValueFallback.None:
case ValueFallback.Default:
return defaultValue;
case ValueFallback.Language:
return TryGetValueWithLanguageFallback(property, culture, segment, defaultValue, out var value2) ? value2 : defaultValue;
default:
throw NotSupportedFallbackMethod(fallback);
switch (f)
{
case Fallback.None:
continue;
case Fallback.DefaultValue:
value = defaultValue;
return true;
case Fallback.Language:
if (TryGetValueWithLanguageFallback(property, culture, segment, defaultValue, out value))
return true;
break;
default:
throw NotSupportedFallbackMethod(f, "property");
}
}
value = defaultValue;
return false;
}
/// <inheritdoc />
public object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue, int fallback)
public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
{
return GetValue<object>(content, alias, culture, segment, defaultValue, fallback);
return TryGetValue<object>(content, alias, culture, segment, fallback, defaultValue, out value);
}
/// <inheritdoc />
public T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue, int fallback)
public bool TryGetValue<T>(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
{
switch (fallback)
foreach (var f in fallback)
{
case ValueFallback.None:
case ValueFallback.Default:
return defaultValue;
case ValueFallback.Language:
return TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out var value2) ? value2 : defaultValue;
default:
throw NotSupportedFallbackMethod(fallback);
switch (f)
{
case Fallback.None:
continue;
case Fallback.DefaultValue:
value = defaultValue;
return true;
case Fallback.Language:
if (TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out value))
return true;
break;
default:
throw NotSupportedFallbackMethod(f, "element");
}
}
value = defaultValue;
return false;
}
/// <inheritdoc />
public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, int fallback)
public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
{
// is that ok?
return GetValue<object>(content, alias, culture, segment, defaultValue, fallback);
return TryGetValue<object>(content, alias, culture, segment, fallback, defaultValue, out value);
}
/// <inheritdoc />
public virtual T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, int fallback)
public virtual bool TryGetValue<T>(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
{
switch (fallback)
// note: we don't support "recurse & language" which would walk up the tree,
// looking at languages at each level - should someone need it... they'll have
// to implement it.
foreach (var f in fallback)
{
case ValueFallback.None:
case ValueFallback.Default:
return defaultValue;
case ValueFallback.Recurse:
return TryGetValueWithRecursiveFallback(content, alias, culture, segment, defaultValue, out var value1) ? value1 : defaultValue;
case ValueFallback.Language:
return TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out var value2) ? value2 : defaultValue;
case ValueFallback.RecurseThenLanguage:
return TryGetValueWithRecursiveFallback(content, alias, culture, segment, defaultValue, out var value3)
? value3
: TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out var value4) ? value4 : defaultValue;
case ValueFallback.LanguageThenRecurse:
return TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out var value5)
? value5
: TryGetValueWithRecursiveFallback(content, alias, culture, segment, defaultValue, out var value6) ? value6 : defaultValue;
default:
throw NotSupportedFallbackMethod(fallback);
switch (f)
{
case Fallback.None:
continue;
case Fallback.DefaultValue:
value = defaultValue;
return true;
case Fallback.Language:
if (TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out value))
return true;
break;
case Fallback.Ancestors:
if (TryGetValueWithRecursiveFallback(content, alias, culture, segment, defaultValue, out value))
return true;
break;
default:
throw NotSupportedFallbackMethod(f, "content");
}
}
value = defaultValue;
return false;
}
private NotSupportedException NotSupportedFallbackMethod(int fallback)
private NotSupportedException NotSupportedFallbackMethod(int fallback, string level)
{
return new NotSupportedException($"Fallback {GetType().Name} does not support policy code '{fallback}'.");
return new NotSupportedException($"Fallback {GetType().Name} does not support fallback code '{fallback}' at {level} level.");
}
// tries to get a value, recursing the tree
+22 -8
View File
@@ -184,7 +184,7 @@ namespace Umbraco.Web
#endregion
#region Value
/// <summary>
/// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value.
/// </summary>
@@ -192,23 +192,30 @@ namespace Umbraco.Web
/// <param name="alias">The property alias.</param>
/// <param name="culture">The variation language.</param>
/// <param name="segment">The variation segment.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="fallback">Optional fallback strategy.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The value of the content's property identified by the alias, if it exists, otherwise a default value.</returns>
public static object Value(this IPublishedContent content, string alias, string culture = null, string segment = null, object defaultValue = default, int fallback = 0)
public static object Value(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default)
{
var property = content.GetProperty(alias);
// if we have a property, and it has a value, return that value
if (property != null && property.HasValue(culture, segment))
return property.GetValue(culture, segment);
return PublishedValueFallback.GetValue(content, alias, culture, segment, defaultValue, fallback);
// else let fallback try to get a value
if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value))
return value;
// else... if we have a property, at least let the converter return its own
// vision of 'no value' (could be an empty enumerable) - otherwise, default
return property?.GetValue(culture, segment);
}
#endregion
#region Value<T>
/// <summary>
/// Gets the value of a content's property identified by its alias, converted to a specified type.
/// </summary>
@@ -217,17 +224,24 @@ namespace Umbraco.Web
/// <param name="alias">The property alias.</param>
/// <param name="culture">The variation language.</param>
/// <param name="segment">The variation segment.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="fallback">Optional fallback strategy.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The value of the content's property identified by the alias, converted to the specified type.</returns>
public static T Value<T>(this IPublishedContent content, string alias, string culture = null, string segment = null, T defaultValue = default, int fallback = 0)
public static T Value<T>(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default)
{
var property = content.GetProperty(alias);
// if we have a property, and it has a value, return that value
if (property != null && property.HasValue(culture, segment))
return property.Value<T>(culture, segment);
return PublishedValueFallback.GetValue(content, alias, culture, segment, defaultValue, fallback);
// else let fallback try to get a value
if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value))
return value;
// else... if we have a property, at least let the converter return its own
// vision of 'no value' (could be an empty enumerable) - otherwise, default
return property == null ? default : property.Value<T>(culture, segment);
}
// fixme - .Value() refactoring - in progress
+19 -10
View File
@@ -99,8 +99,8 @@ namespace Umbraco.Web
/// <param name="alias">The property alias.</param>
/// <param name="culture">The variation language.</param>
/// <param name="segment">The variation segment.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="fallback">Optional fallback strategy.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The value of the content's property identified by the alias, if it exists, otherwise a default value.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
@@ -108,19 +108,21 @@ namespace Umbraco.Web
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static object Value(this IPublishedElement content, string alias, string culture = null, string segment = null, object defaultValue = default, int fallback = 0)
public static object Value(this IPublishedElement content, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default)
{
var property = content.GetProperty(alias);
// if we have a property, and it has a value, return that value
if (property != null && property.HasValue(culture, segment))
return property.GetValue(culture, segment);
// fixme defaultValue is a problem here
// assuming the value may return as an IEnumerable<int> and no defaultValue is provided, then defaultValue is null
// and if HasValue is false, what we get is 'null' - but the converter may instead have been able to return an
// empty enumerable, which would be way nicer - so we need a way to tell that 'no defaultValue has been provided'?
// else let fallback try to get a value
if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value))
return value;
return PublishedValueFallback.GetValue(content, alias, culture, segment, defaultValue, fallback);
// else... if we have a property, at least let the converter return its own
// vision of 'no value' (could be an empty enumerable) - otherwise, default
return property?.GetValue(culture, segment);
}
#endregion
@@ -135,8 +137,8 @@ namespace Umbraco.Web
/// <param name="alias">The property alias.</param>
/// <param name="culture">The variation language.</param>
/// <param name="segment">The variation segment.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="fallback">Optional fallback strategy.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The value of the content's property identified by the alias, converted to the specified type.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
@@ -144,14 +146,21 @@ namespace Umbraco.Web
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
public static T Value<T>(this IPublishedElement content, string alias, string culture = null, string segment = null, T defaultValue = default, int fallback = 0)
public static T Value<T>(this IPublishedElement content, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default)
{
var property = content.GetProperty(alias);
// if we have a property, and it has a value, return that value
if (property != null && property.HasValue(culture, segment))
return property.Value<T>(culture, segment);
return PublishedValueFallback.GetValue<T>(content, alias, culture, segment, defaultValue, fallback);
// else let fallback try to get a value
if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value))
return value;
// else... if we have a property, at least let the converter return its own
// vision of 'no value' (could be an empty enumerable) - otherwise, default
return property == null ? default : property.Value<T>(culture, segment);
}
#endregion
+28 -35
View File
@@ -16,56 +16,49 @@ namespace Umbraco.Web
#region Value
public static object Value(this IPublishedProperty property, string culture = null, string segment = null, object defaultValue = default, int fallback = 0)
public static object Value(this IPublishedProperty property, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default)
{
if (property.HasValue(culture, segment))
return property.GetValue(culture, segment);
return PublishedValueFallback.GetValue(property, culture, segment, defaultValue, fallback);
return PublishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var value)
? value
: property.GetValue(culture, segment); // give converter a chance to return it's own vision of "no value"
}
#endregion
#region Value<T>
public static T Value<T>(this IPublishedProperty property, string culture = null, string segment = null, T defaultValue = default, int fallback = 0)
public static T Value<T>(this IPublishedProperty property, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default)
{
// for Value<T> when defaultValue is not specified, and HasValue() is false, we still want to convert the result (see below)
// but we have no way to tell whether default value is specified or not - we could do it with overloads, but then defaultValue
// comes right after property and conflicts with culture when T is string - so we're just not doing it - if defaultValue is
// default, whether specified or not, we give a chance to the converter
//
//if (!property.HasValue(culture, segment) && 'defaultValue is explicitely specified') return defaultValue;
if (property.HasValue(culture, segment))
{
// we have a value
// try to cast or convert it
var value = property.GetValue(culture, segment);
if (value is T valueAsT) return valueAsT;
var valueConverted = value.TryConvertTo<T>();
if (valueConverted) return valueConverted.Result;
// give the converter a chance to handle the default value differently
// eg for IEnumerable<T> it may return Enumerable<T>.Empty instead of null
// cannot cast nor convert the value, nothing we can return but 'default'
// note: we don't want to fallback in that case - would make little sense
return default;
}
var value = property.GetValue(culture, segment);
// we don't have a value, try fallback
if (PublishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var fallbackValue))
return fallbackValue;
// if value is null (strange but why not) it still is OK to call TryConvertTo
// because it's an extension method (hence no NullRef) which will return a
// failed attempt. So, no need to care for value being null here.
// we don't have a value - neither direct nor fallback
// give a chance to the converter to return something (eg empty enumerable)
var noValue = property.GetValue(culture, segment);
if (noValue is T noValueAsT) return noValueAsT;
var noValueConverted = noValue.TryConvertTo<T>();
if (noValueConverted) return noValueConverted.Result;
// if already the requested type, return
if (value is T variable) return variable;
// if can convert to requested type, return
var convert = value.TryConvertTo<T>();
if (convert.Success) return convert.Result;
// at that point, the code tried with the raw value
// that makes no sense because it sort of is unpredictable,
// you wouldn't know when the converters run or don't run.
// so, it's commented out now.
// try with the raw value
//var source = property.ValueSource;
//if (source is string) source = TextValueConverterHelper.ParseStringValueSource((string)source);
//if (source is T) return (T)source;
//convert = source.TryConvertTo<T>();
//if (convert.Success) return convert.Result;
return PublishedValueFallback.GetValue(property, culture, segment, defaultValue, fallback);
// cannot cast noValue nor convert it, nothing we can return but 'default'
return default;
}
#endregion
+2 -2
View File
@@ -82,7 +82,7 @@ namespace umbraco
//check for published content and get its value using that
if (publishedContent != null && (publishedContent.HasProperty(_fieldName) || recursive))
{
var pval = publishedContent.Value(_fieldName, fallback: Constants.Content.ValueFallback.Recurse);
var pval = publishedContent.Value(_fieldName, fallback: Fallback.ToAncestors);
var rval = pval == null ? string.Empty : pval.ToString();
_fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval;
}
@@ -102,7 +102,7 @@ namespace umbraco
{
if (publishedContent != null && (publishedContent.HasProperty(altFieldName) || recursive))
{
var pval = publishedContent.Value(altFieldName, fallback: Constants.Content.ValueFallback.Recurse);
var pval = publishedContent.Value(altFieldName, fallback: Fallback.ToAncestors);
var rval = pval == null ? string.Empty : pval.ToString();
_fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval;
}