Merge branch 'release/13.3.0' into v13/dev
This commit is contained in:
@@ -12,23 +12,36 @@ public class DeliveryApiJsonTypeResolver : DefaultJsonTypeInfoResolver
|
||||
{
|
||||
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);
|
||||
|
||||
if (jsonTypeInfo.Type == typeof(IApiContent))
|
||||
Type[] derivedTypes = GetDerivedTypes(jsonTypeInfo);
|
||||
if (derivedTypes.Length > 0)
|
||||
{
|
||||
ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(ApiContent));
|
||||
}
|
||||
else if (jsonTypeInfo.Type == typeof(IApiContentResponse))
|
||||
{
|
||||
ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(ApiContentResponse));
|
||||
}
|
||||
else if (jsonTypeInfo.Type == typeof(IRichTextElement))
|
||||
{
|
||||
ConfigureJsonPolymorphismOptions(jsonTypeInfo, typeof(RichTextRootElement), typeof(RichTextGenericElement), typeof(RichTextTextElement));
|
||||
ConfigureJsonPolymorphismOptions(jsonTypeInfo, derivedTypes);
|
||||
}
|
||||
|
||||
return jsonTypeInfo;
|
||||
}
|
||||
|
||||
private void ConfigureJsonPolymorphismOptions(JsonTypeInfo jsonTypeInfo, params Type[] derivedTypes)
|
||||
protected virtual Type[] GetDerivedTypes(JsonTypeInfo jsonTypeInfo)
|
||||
{
|
||||
if (jsonTypeInfo.Type == typeof(IApiContent))
|
||||
{
|
||||
return new[] { typeof(ApiContent) };
|
||||
}
|
||||
|
||||
if (jsonTypeInfo.Type == typeof(IApiContentResponse))
|
||||
{
|
||||
return new[] { typeof(ApiContentResponse) };
|
||||
}
|
||||
|
||||
if (jsonTypeInfo.Type == typeof(IRichTextElement))
|
||||
{
|
||||
return new[] { typeof(RichTextRootElement), typeof(RichTextGenericElement), typeof(RichTextTextElement) };
|
||||
}
|
||||
|
||||
return Array.Empty<Type>();
|
||||
}
|
||||
|
||||
protected void ConfigureJsonPolymorphismOptions(JsonTypeInfo jsonTypeInfo, params Type[] derivedTypes)
|
||||
{
|
||||
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
|
||||
{
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using Umbraco.Cms.Core.Models.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public sealed class ApiContentResponseBuilder : ApiContentBuilderBase<IApiContentResponse>, IApiContentResponseBuilder
|
||||
public class ApiContentResponseBuilder : ApiContentBuilderBase<IApiContentResponse>, IApiContentResponseBuilder
|
||||
{
|
||||
private readonly IApiContentRouteBuilder _apiContentRouteBuilder;
|
||||
|
||||
@@ -14,6 +13,12 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBase<IApiConten
|
||||
=> _apiContentRouteBuilder = apiContentRouteBuilder;
|
||||
|
||||
protected override IApiContentResponse Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary<string, object?> properties)
|
||||
{
|
||||
IDictionary<string, IApiContentRoute> cultures = GetCultures(content);
|
||||
return new ApiContentResponse(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties, cultures);
|
||||
}
|
||||
|
||||
protected virtual IDictionary<string, IApiContentRoute> GetCultures(IPublishedContent content)
|
||||
{
|
||||
var routesByCulture = new Dictionary<string, IApiContentRoute>();
|
||||
|
||||
@@ -35,6 +40,6 @@ public sealed class ApiContentResponseBuilder : ApiContentBuilderBase<IApiConten
|
||||
routesByCulture[publishedCultureInfo.Culture] = cultureRoute;
|
||||
}
|
||||
|
||||
return new ApiContentResponse(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties, routesByCulture);
|
||||
return routesByCulture;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Xml.Serialization;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Collections;
|
||||
@@ -19,7 +20,6 @@ internal class Property : PublishedPropertyBase
|
||||
private readonly bool _isMember;
|
||||
private readonly bool _isPreviewing;
|
||||
|
||||
private readonly object _locko = new();
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
|
||||
// the invariant-neutral source and inter values
|
||||
@@ -33,7 +33,8 @@ internal class Property : PublishedPropertyBase
|
||||
private object? _interValue;
|
||||
|
||||
// the variant source and inter values
|
||||
private Dictionary<CompositeStringStringKey, SourceInterValue>? _sourceValues;
|
||||
private readonly object _locko = new();
|
||||
private ConcurrentDictionary<CompositeStringStringKey, SourceInterValue>? _sourceValues;
|
||||
|
||||
private string? _valuesCacheKey;
|
||||
|
||||
@@ -66,12 +67,9 @@ internal class Property : PublishedPropertyBase
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_sourceValues == null)
|
||||
{
|
||||
_sourceValues = new Dictionary<CompositeStringStringKey, SourceInterValue>();
|
||||
}
|
||||
EnsureSourceValuesInitialized();
|
||||
|
||||
_sourceValues[new CompositeStringStringKey(sourceValue.Culture, sourceValue.Segment)]
|
||||
_sourceValues![new CompositeStringStringKey(sourceValue.Culture, sourceValue.Segment)]
|
||||
= new SourceInterValue
|
||||
{
|
||||
Culture = sourceValue.Culture,
|
||||
@@ -125,30 +123,27 @@ internal class Property : PublishedPropertyBase
|
||||
return hasValue.Value;
|
||||
}
|
||||
|
||||
lock (_locko)
|
||||
value = GetInterValue(culture, segment);
|
||||
hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter);
|
||||
if (hasValue.HasValue)
|
||||
{
|
||||
value = GetInterValue(culture, segment);
|
||||
hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter);
|
||||
if (hasValue.HasValue)
|
||||
{
|
||||
return hasValue.Value;
|
||||
}
|
||||
|
||||
CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment);
|
||||
|
||||
// initial reference cache level always is .Content
|
||||
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
|
||||
|
||||
if (!cacheValues.ObjectInitialized)
|
||||
{
|
||||
cacheValues.ObjectValue =
|
||||
PropertyType.ConvertInterToObject(_content, initialCacheLevel, value, _isPreviewing);
|
||||
cacheValues.ObjectInitialized = true;
|
||||
}
|
||||
|
||||
value = cacheValues.ObjectValue;
|
||||
return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false;
|
||||
return hasValue.Value;
|
||||
}
|
||||
|
||||
CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment);
|
||||
|
||||
// initial reference cache level always is .Content
|
||||
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
|
||||
|
||||
if (!cacheValues.ObjectInitialized)
|
||||
{
|
||||
cacheValues.ObjectValue =
|
||||
PropertyType.ConvertInterToObject(_content, initialCacheLevel, value, _isPreviewing);
|
||||
cacheValues.ObjectInitialized = true;
|
||||
}
|
||||
|
||||
value = cacheValues.ObjectValue;
|
||||
return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false;
|
||||
}
|
||||
|
||||
public override object? GetSourceValue(string? culture = null, string? segment = null)
|
||||
@@ -160,19 +155,16 @@ internal class Property : PublishedPropertyBase
|
||||
return _sourceValue;
|
||||
}
|
||||
|
||||
lock (_locko)
|
||||
if (_sourceValues == null)
|
||||
{
|
||||
if (_sourceValues == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _sourceValues.TryGetValue(
|
||||
new CompositeStringStringKey(culture, segment),
|
||||
out SourceInterValue? sourceValue)
|
||||
? sourceValue.SourceValue
|
||||
: null;
|
||||
return null;
|
||||
}
|
||||
|
||||
return _sourceValues.TryGetValue(
|
||||
new CompositeStringStringKey(culture, segment),
|
||||
out SourceInterValue? sourceValue)
|
||||
? sourceValue.SourceValue
|
||||
: null;
|
||||
}
|
||||
|
||||
private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel)
|
||||
@@ -227,7 +219,6 @@ internal class Property : PublishedPropertyBase
|
||||
return (CacheValues)cache.Get(ValuesCacheKey, () => new CacheValues())!;
|
||||
}
|
||||
|
||||
// this is always invoked from within a lock, so does not require its own lock
|
||||
private object? GetInterValue(string? culture, string? segment)
|
||||
{
|
||||
if (culture == string.Empty && segment == string.Empty)
|
||||
@@ -242,21 +233,17 @@ internal class Property : PublishedPropertyBase
|
||||
return _interValue;
|
||||
}
|
||||
|
||||
if (_sourceValues == null)
|
||||
{
|
||||
_sourceValues = new Dictionary<CompositeStringStringKey, SourceInterValue>();
|
||||
}
|
||||
EnsureSourceValuesInitialized();
|
||||
|
||||
var k = new CompositeStringStringKey(culture, segment);
|
||||
if (!_sourceValues.TryGetValue(k, out SourceInterValue? vvalue))
|
||||
{
|
||||
_sourceValues[k] = vvalue = new SourceInterValue
|
||||
|
||||
SourceInterValue vvalue = _sourceValues!.GetOrAdd(k, _ =>
|
||||
new SourceInterValue
|
||||
{
|
||||
Culture = culture,
|
||||
Segment = segment,
|
||||
SourceValue = GetSourceValue(culture, segment),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (vvalue.InterInitialized)
|
||||
{
|
||||
@@ -273,23 +260,20 @@ internal class Property : PublishedPropertyBase
|
||||
_content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment);
|
||||
|
||||
object? value;
|
||||
lock (_locko)
|
||||
CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment);
|
||||
|
||||
// initial reference cache level always is .Content
|
||||
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
|
||||
|
||||
if (cacheValues.ObjectInitialized)
|
||||
{
|
||||
CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment);
|
||||
|
||||
// initial reference cache level always is .Content
|
||||
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
|
||||
|
||||
if (cacheValues.ObjectInitialized)
|
||||
{
|
||||
return cacheValues.ObjectValue;
|
||||
}
|
||||
|
||||
cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing);
|
||||
cacheValues.ObjectInitialized = true;
|
||||
value = cacheValues.ObjectValue;
|
||||
return cacheValues.ObjectValue;
|
||||
}
|
||||
|
||||
cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing);
|
||||
cacheValues.ObjectInitialized = true;
|
||||
value = cacheValues.ObjectValue;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -298,22 +282,19 @@ internal class Property : PublishedPropertyBase
|
||||
{
|
||||
_content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment);
|
||||
|
||||
lock (_locko)
|
||||
CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment);
|
||||
|
||||
// initial reference cache level always is .Content
|
||||
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
|
||||
|
||||
if (cacheValues.XPathInitialized)
|
||||
{
|
||||
CacheValue cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment);
|
||||
|
||||
// initial reference cache level always is .Content
|
||||
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
|
||||
|
||||
if (cacheValues.XPathInitialized)
|
||||
{
|
||||
return cacheValues.XPathValue;
|
||||
}
|
||||
|
||||
cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing);
|
||||
cacheValues.XPathInitialized = true;
|
||||
return cacheValues.XPathValue;
|
||||
}
|
||||
|
||||
cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing);
|
||||
cacheValues.XPathInitialized = true;
|
||||
return cacheValues.XPathValue;
|
||||
}
|
||||
|
||||
public override object? GetDeliveryApiValue(bool expanding, string? culture = null, string? segment = null)
|
||||
@@ -321,18 +302,16 @@ internal class Property : PublishedPropertyBase
|
||||
_content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment);
|
||||
|
||||
object? value;
|
||||
lock (_locko)
|
||||
{
|
||||
CacheValue cacheValues = GetCacheValues(expanding ? PropertyType.DeliveryApiCacheLevelForExpansion : PropertyType.DeliveryApiCacheLevel).For(culture, segment);
|
||||
CacheValue cacheValues = GetCacheValues(expanding ? PropertyType.DeliveryApiCacheLevelForExpansion : PropertyType.DeliveryApiCacheLevel).For(culture, segment);
|
||||
|
||||
// initial reference cache level always is .Content
|
||||
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
|
||||
|
||||
object? GetDeliveryApiObject() => PropertyType.ConvertInterToDeliveryApiObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing, expanding);
|
||||
value = expanding
|
||||
? GetDeliveryApiExpandedObject(cacheValues, GetDeliveryApiObject)
|
||||
: GetDeliveryApiDefaultObject(cacheValues, GetDeliveryApiObject);
|
||||
}
|
||||
// initial reference cache level always is .Content
|
||||
const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element;
|
||||
|
||||
object? GetDeliveryApiObject() => PropertyType.ConvertInterToDeliveryApiObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing, expanding);
|
||||
value = expanding
|
||||
? GetDeliveryApiExpandedObject(cacheValues, GetDeliveryApiObject)
|
||||
: GetDeliveryApiDefaultObject(cacheValues, GetDeliveryApiObject);
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -382,9 +361,9 @@ internal class Property : PublishedPropertyBase
|
||||
|
||||
private class CacheValues : CacheValue
|
||||
{
|
||||
private Dictionary<CompositeStringStringKey, CacheValue>? _values;
|
||||
private readonly object _locko = new();
|
||||
private ConcurrentDictionary<CompositeStringStringKey, CacheValue>? _values;
|
||||
|
||||
// this is always invoked from within a lock, so does not require its own lock
|
||||
public CacheValue For(string? culture, string? segment)
|
||||
{
|
||||
if (culture == string.Empty && segment == string.Empty)
|
||||
@@ -394,14 +373,15 @@ internal class Property : PublishedPropertyBase
|
||||
|
||||
if (_values == null)
|
||||
{
|
||||
_values = new Dictionary<CompositeStringStringKey, CacheValue>();
|
||||
lock (_locko)
|
||||
{
|
||||
_values ??= InitializeConcurrentDictionary<CompositeStringStringKey, CacheValue>();
|
||||
}
|
||||
}
|
||||
|
||||
var k = new CompositeStringStringKey(culture, segment);
|
||||
if (!_values.TryGetValue(k, out CacheValue? value))
|
||||
{
|
||||
_values[k] = value = new CacheValue();
|
||||
}
|
||||
|
||||
CacheValue value = _values.GetOrAdd(k, _ => new CacheValue());
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -431,5 +411,22 @@ internal class Property : PublishedPropertyBase
|
||||
public object? InterValue { get; set; }
|
||||
}
|
||||
|
||||
private static ConcurrentDictionary<TKey, TValue> InitializeConcurrentDictionary<TKey, TValue>()
|
||||
where TKey : notnull
|
||||
=> new(-1, 5);
|
||||
|
||||
private void EnsureSourceValuesInitialized()
|
||||
{
|
||||
if (_sourceValues is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_locko)
|
||||
{
|
||||
_sourceValues ??= InitializeConcurrentDictionary<CompositeStringStringKey, SourceInterValue>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
|
||||
"version": "13.3.0-rc",
|
||||
"version": "13.3.0",
|
||||
"assemblyVersion": {
|
||||
"precision": "build"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user