Allows serializer to be configured

This commit is contained in:
Shannon
2020-07-03 13:30:40 +10:00
parent c63bfb866b
commit 9b827df11a
8 changed files with 152 additions and 22 deletions

View File

@@ -19,8 +19,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
VersionDate = PrimitiveSerializer.DateTime.ReadFrom(stream),
WriterId = PrimitiveSerializer.Int32.ReadFrom(stream),
TemplateId = PrimitiveSerializer.Int32.ReadFrom(stream),
Properties = PropertiesSerializer.ReadFrom(stream),
CultureInfos = CultureVariationsSerializer.ReadFrom(stream)
Properties = PropertiesSerializer.ReadFrom(stream), // TODO: We don't want to allocate empty arrays
CultureInfos = CultureVariationsSerializer.ReadFrom(stream) // TODO: We don't want to allocate empty arrays
};
}

View File

@@ -1,5 +1,7 @@
using Newtonsoft.Json;
using MessagePack;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Serialization;
namespace Umbraco.Web.PublishedCache.NuCache.DataSource
@@ -7,29 +9,37 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
/// <summary>
/// The content item 1:M data that is serialized to JSON
/// </summary>
[DataContract] // NOTE: Use DataContract annotations here to control how MessagePack serializes/deserializes the data to use INT keys
public class ContentNestedData
{
// TODO: We don't want to allocate empty arrays
//dont serialize empty properties
[DataMember(Order = 0)]
[JsonProperty("pd")]
[JsonConverter(typeof(AutoInterningStringKeyCaseInsensitiveDictionaryConverter<PropertyData[]>))]
public Dictionary<string, PropertyData[]> PropertyData { get; set; }
[DataMember(Order = 1)]
[JsonProperty("cd")]
[JsonConverter(typeof(AutoInterningStringKeyCaseInsensitiveDictionaryConverter<CultureVariation>))]
public Dictionary<string, CultureVariation> CultureData { get; set; }
[DataMember(Order = 2)]
[JsonProperty("us")]
public string UrlSegment { get; set; }
//Legacy properties used to deserialize existing nucache db entries
[DataMember(Order = 3)]
[JsonProperty("properties")]
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<PropertyData[]>))]
private Dictionary<string, PropertyData[]> LegacyPropertyData { set { PropertyData = value; } }
[DataMember(Order = 4)]
[JsonProperty("cultureData")]
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<CultureVariation>))]
private Dictionary<string, CultureVariation> LegacyCultureData { set { CultureData = value; } }
[DataMember(Order = 5)]
[JsonProperty("urlSegment")]
private string LegacyUrlSegment { set { UrlSegment = value; } }
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace Umbraco.Web.PublishedCache.NuCache.DataSource
@@ -6,30 +7,39 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
/// <summary>
/// Represents the culture variation information on a content item
/// </summary>
[DataContract] // NOTE: Use DataContract annotations here to control how MessagePack serializes/deserializes the data to use INT keys
public class CultureVariation
{
[DataMember(Order = 0)]
[JsonProperty("nm")]
public string Name { get; set; }
[DataMember(Order = 1)]
[JsonProperty("us")]
public string UrlSegment { get; set; }
[DataMember(Order = 2)]
[JsonProperty("dt")]
public DateTime Date { get; set; }
[DataMember(Order = 3)]
[JsonProperty("isd")]
public bool IsDraft { get; set; }
//Legacy properties used to deserialize existing nucache db entries
[DataMember(Order = 4)]
[JsonProperty("name")]
private string LegacyName { set { Name = value; } }
[DataMember(Order = 5)]
[JsonProperty("urlSegment")]
private string LegacyUrlSegment { set { UrlSegment = value; } }
[DataMember(Order = 6)]
[JsonProperty("date")]
private DateTime LegacyDate { set { Date = value; } }
[DataMember(Order = 7)]
[JsonProperty("isDraft")]
private bool LegacyIsDraft { set { IsDraft = value; } }
}

View File

@@ -20,12 +20,10 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
// provides efficient database access for NuCache
internal class DatabaseDataSource : IDataSource
{
private const int PageSize = 500;
private readonly IContentNestedDataSerializer _contentNestedDataSerializer;
internal DatabaseDataSource(IContentNestedDataSerializer contentNestedDataSerializer)
public DatabaseDataSource(IContentNestedDataSerializer contentNestedDataSerializer)
{
_contentNestedDataSerializer = contentNestedDataSerializer;
}
@@ -231,7 +229,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
VersionId = dto.VersionId,
VersionDate = dto.EditVersionDate,
WriterId = dto.EditWriterId,
Properties = nested.PropertyData,
Properties = nested.PropertyData, // TODO: We don't want to allocate empty arrays
CultureInfos = nested.CultureData,
UrlSegment = nested.UrlSegment
};
@@ -259,7 +257,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
VersionId = dto.VersionId,
VersionDate = dto.PubVersionDate,
WriterId = dto.PubWriterId,
Properties = nested.PropertyData,
Properties = nested.PropertyData, // TODO: We don't want to allocate empty arrays
CultureInfos = nested.CultureData
};
}
@@ -294,7 +292,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
VersionId = dto.VersionId,
VersionDate = dto.EditVersionDate,
WriterId = dto.CreatorId, // what-else?
Properties = nested.PropertyData,
Properties = nested.PropertyData, // TODO: We don't want to allocate empty arrays
CultureInfos = nested.CultureData
};

View File

@@ -1,5 +1,8 @@
using MessagePack;
using MessagePack.Formatters;
using MessagePack.Resolvers;
using System;
using System.Collections.Generic;
namespace Umbraco.Web.PublishedCache.NuCache.DataSource
{
@@ -9,7 +12,24 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
public MsgPackContentNestedDataSerializer()
{
_options = MessagePack.Resolvers.ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4BlockArray);
var defaultOptions = ContractlessStandardResolver.Options;
var resolver = CompositeResolver.Create(
// TODO: We want to be able to intern the strings for aliases when deserializing like we do for Newtonsoft but I'm unsure exactly how
// to do that but it would seem to be with a custom message pack resolver but I haven't quite figured out based on the docs how
// to do that since that is part of the int key -> string mapping operation, might have to see the source code to figure that one out.
// resolver custom types first
// new ContentNestedDataResolver(),
// finally use standard resolver
defaultOptions.Resolver
);
_options = defaultOptions
.WithResolver(resolver)
.WithCompression(MessagePackCompression.Lz4BlockArray);
}
public string ToJson(string serialized)
@@ -21,9 +41,6 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
// TODO: Instead of returning base64 it would be more ideal to avoid that translation entirely and just store/retrieve raw bytes
// TODO: We need to write tests to serialize/deserialize between either of these serializers to ensure we end up with the same object
// i think this one is a bit quirky so far :)
public ContentNestedData Deserialize(string data)
{
var bin = Convert.FromBase64String(data);
@@ -32,11 +49,89 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
}
public string Serialize(ContentNestedData nestedData)
{
var bin = MessagePackSerializer.Serialize(
nestedData,
_options);
{
var bin = MessagePackSerializer.Serialize(nestedData, _options);
return Convert.ToBase64String(bin);
}
//private class ContentNestedDataResolver : IFormatterResolver
//{
// // GetFormatter<T>'s get cost should be minimized so use type cache.
// public IMessagePackFormatter<T> GetFormatter<T>() => FormatterCache<T>.Formatter;
// private static class FormatterCache<T>
// {
// public static readonly IMessagePackFormatter<T> Formatter;
// // generic's static constructor should be minimized for reduce type generation size!
// // use outer helper method.
// static FormatterCache()
// {
// Formatter = (IMessagePackFormatter<T>)SampleCustomResolverGetFormatterHelper.GetFormatter(typeof(T));
// }
// }
//}
//internal static class SampleCustomResolverGetFormatterHelper
//{
// // If type is concrete type, use type-formatter map
// static readonly Dictionary<Type, object> _formatterMap = new Dictionary<Type, object>()
// {
// {typeof(ContentNestedData), new ContentNestedDataFormatter()}
// // add more your own custom serializers.
// };
// internal static object GetFormatter(Type t)
// {
// object formatter;
// if (_formatterMap.TryGetValue(t, out formatter))
// {
// return formatter;
// }
// // If target type is generics, use MakeGenericType.
// if (t.IsGenericParameter && t.GetGenericTypeDefinition() == typeof(ValueTuple<,>))
// {
// return Activator.CreateInstance(typeof(ValueTupleFormatter<,>).MakeGenericType(t.GenericTypeArguments));
// }
// // If type can not get, must return null for fallback mechanism.
// return null;
// }
//}
//public class ContentNestedDataFormatter : IMessagePackFormatter<ContentNestedData>
//{
// public void Serialize(ref MessagePackWriter writer, ContentNestedData value, MessagePackSerializerOptions options)
// {
// if (value == null)
// {
// writer.WriteNil();
// return;
// }
// writer.WriteArrayHeader(3);
// writer.WriteString(value.UrlSegment);
// writer.WriteString(value.FullName);
// writer.WriteString(value.Age);
// writer.WriteString(value.FullName);
// }
// public ContentNestedData Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
// {
// if (reader.TryReadNil())
// {
// return null;
// }
// options.Security.DepthStep(ref reader);
// var path = reader.ReadString();
// reader.Depth--;
// return new FileInfo(path);
// }
//}
}
}

View File

@@ -1,15 +1,19 @@
using System;
using System.ComponentModel;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Umbraco.Core.Serialization;
namespace Umbraco.Web.PublishedCache.NuCache.DataSource
{
[DataContract] // NOTE: Use DataContract annotations here to control how MessagePack serializes/deserializes the data to use INT keys
public class PropertyData
{
private string _culture;
private string _segment;
[DataMember(Order = 0)]
[JsonConverter(typeof(AutoInterningStringConverter))]
[DefaultValue("")]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "c")]
@@ -19,6 +23,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
set => _culture = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null
}
[DataMember(Order = 1)]
[JsonConverter(typeof(AutoInterningStringConverter))]
[DefaultValue("")]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "s")]
@@ -28,22 +33,26 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
set => _segment = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null
}
[DataMember(Order = 2)]
[JsonProperty("v")]
public object Value { get; set; }
//Legacy properties used to deserialize existing nucache db entries
[DataMember(Order = 3)]
[JsonProperty("culture")]
private string LegacyCulture
{
set => Culture = value;
}
[DataMember(Order = 4)]
[JsonProperty("seg")]
private string LegacySegment
{
set => Segment = value;
}
[DataMember(Order = 5)]
[JsonProperty("val")]
private object LegacyValue
{