Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use System.Text.Json as a backend for view model serialization #1799

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f88e21d
Initial migration to System.Text.Json
exyi Mar 14, 2024
9da17ea
Fix microsoft NuGet packages versions to 6.0.0, unless newer is needed
exyi Mar 14, 2024
8738152
STJ: fix some UI tests
exyi Mar 14, 2024
2262709
STJ migration: fix view model size warning
exyi Mar 15, 2024
e8e5cd1
STJ migration: fix server-side view model cache
exyi Mar 17, 2024
ef5a759
Fixed OWIN sample dependencies and binding redirects
tomasherceg Mar 22, 2024
168af46
STJ migration: remove Newtonsoft, rewrite static command plans
exyi Apr 15, 2024
d69641d
STJ migration: apply suggestions from code review
exyi Apr 24, 2024
aad5dfc
Remove Newtonsoft.Json.Linq usage from unit tests
exyi Apr 24, 2024
1bafb79
STJ migration: Add support for field serialization for ValueTuple
exyi Apr 24, 2024
fd1f83a
STJ migration: ignore DotvvmConfiguration deserialization tests
exyi Apr 25, 2024
c90af83
STJ: add proper support for property shadowing
exyi Apr 25, 2024
63d8471
STJ: test for static dispatch interface serialization
exyi Apr 26, 2024
e469e00
STJ: Fix dynamic dispatch in deserialization
exyi Apr 26, 2024
8c4bcbf
Add Strykker configuration
exyi Apr 26, 2024
dcba751
STJ: remove custom DateOnly/TimeOnly converters
exyi Apr 26, 2024
3b3a365
STJ: support custom converters for view models
exyi Apr 26, 2024
5fa835e
STJ: remove redundant code, small code review fixes
exyi Apr 27, 2024
abf5ce9
STJ migration: apply code review suggestions
exyi May 23, 2024
f7fa2e7
STJ: bit more tests for enum converter
exyi May 23, 2024
5b5b962
Merge remote-tracking branch 'origin/main' into system.text.json
exyi Jul 12, 2024
de8be69
Update STJ to 8.0.3
exyi Jul 12, 2024
07ae139
Try to fix tests on .NET Framework
exyi Jul 12, 2024
6f01776
STJ migration: add back DateOnly, TimeOnly converters for .NET Framework
exyi Jul 12, 2024
6020b0a
Merge remote-tracking branch 'origin/main' into system.text.json
exyi Oct 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ jobs:
# target-framework: net7.0
- name: Tests (net472)
uses: ./.github/unittest
if: matrix.os == 'windows-2022'
if: ${{ (matrix.os == 'windows-2022') && (success() || failure()) }}
with:
project: src/Tests
name: framework-tests
Expand All @@ -126,6 +126,7 @@ jobs:
target-framework: net472
- name: Analyzers.Tests (net6.0)
uses: ./.github/unittest
if: ${{ success() || failure() }}
with:
project: src/Analyzers/Analyzers.Tests
name: analyzers-tests
Expand Down
2 changes: 1 addition & 1 deletion src/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
]
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Writers;
using Newtonsoft.Json;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace DotVVM.AutoUI.Metadata
public class ResourceViewModelValidationMetadataProvider : IViewModelValidationMetadataProvider
{
private readonly IViewModelValidationMetadataProvider baseValidationMetadataProvider;
private readonly ConcurrentDictionary<PropertyInfo, List<ValidationAttribute>> cache = new();
private readonly ConcurrentDictionary<MemberInfo, ValidationAttribute[]> cache = new();
private readonly ResourceManager errorMessages;
private static readonly FieldInfo internalErrorMessageField;

Expand All @@ -43,15 +43,15 @@ static ResourceViewModelValidationMetadataProvider()
/// <summary>
/// Gets validation attributes for the specified property.
/// </summary>
public IEnumerable<ValidationAttribute> GetAttributesForProperty(PropertyInfo property)
public IEnumerable<ValidationAttribute> GetAttributesForProperty(MemberInfo property)
{
return cache.GetOrAdd(property, GetAttributesForPropertyCore);
}

/// <summary>
/// Determines validation attributes for the specified property and loads missing error messages from the resource file.
/// </summary>
private List<ValidationAttribute> GetAttributesForPropertyCore(PropertyInfo property)
private ValidationAttribute[] GetAttributesForPropertyCore(MemberInfo property)
{
// process all validation attributes
var results = new List<ValidationAttribute>();
Expand All @@ -73,7 +73,7 @@ private List<ValidationAttribute> GetAttributesForPropertyCore(PropertyInfo prop
}
}

return results;
return results.Count == 0 ? Array.Empty<ValidationAttribute>() : results.ToArray();
}

private bool HasDefaultErrorMessage(ValidationAttribute attribute)
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</PropertyGroup>

<PropertyGroup Label="Building">
<LangVersion>11.0</LangVersion>
<LangVersion>12.0</LangVersion>
<!-- Disable warning for missing XML doc comments. -->
<NoWarn>$(NoWarn);CS1591;CS1573</NoWarn>
<Deterministic>true</Deterministic>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ public class PropertyDisplayMetadata
public StyleAttribute Styles { get; set; }
public bool IsEditAllowed { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Threading;
Expand Down Expand Up @@ -40,8 +41,12 @@ public ResourceViewModelValidationMetadataProvider(Type errorMessagesResourceFil
/// <summary>
/// Gets validation attributes for the specified property.
/// </summary>
public IEnumerable<ValidationAttribute> GetAttributesForProperty(PropertyInfo property)
public IEnumerable<ValidationAttribute> GetAttributesForProperty(MemberInfo member)
{
if (member is not PropertyInfo property)
{
return [];
}
return cache.GetOrAdd(new PropertyInfoCulturePair(CultureInfo.CurrentUICulture, property), GetAttributesForPropertyCore);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Framework/Core/DotVVM.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<EmbeddedResource Include="compiler\resources\**\*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Text.Json" Version="8.0.3" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<PackageReference Include="System.ComponentModel.Annotations" Version="4.3.0" />
Expand Down
13 changes: 12 additions & 1 deletion src/Framework/Core/ViewModel/BindAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace DotVVM.Framework.ViewModel
/// <summary>
/// Specifies the binding direction.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class BindAttribute : Attribute
{

Expand All @@ -21,6 +21,17 @@ public class BindAttribute : Attribute
/// </summary>
public string? Name { get; set; }

public bool? _allowDynamicDispatch;
/// <summary>
/// When true, DotVVM serializer will select the JSON converter based on the runtime type, instead of deciding it ahead of time.
/// This essentially enables serialization of properties defined derived types, but does not enable derive type deserialization, unless an instance of the correct type is prepopulated into the property.
/// By default, dynamic dispatch is enabled for abstract types (including interfaces and System.Object).
/// </summary>
public bool AllowDynamicDispatch { get => _allowDynamicDispatch ?? false; set => _allowDynamicDispatch = value; }
tomasherceg marked this conversation as resolved.
Show resolved Hide resolved

/// <summary> See <see cref="AllowDynamicDispatch" /> </summary>
public bool AllowsDynamicDispatch(bool defaultValue) => _allowDynamicDispatch ?? defaultValue;


/// <summary>
/// Initializes a new instance of the <see cref="BindAttribute"/> class.
Expand Down
29 changes: 21 additions & 8 deletions src/Framework/Core/ViewModel/DefaultPropertySerialization.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System.Reflection;
using Newtonsoft.Json;
using System;
using System.Reflection;
using System.Text.Json.Serialization;

namespace DotVVM.Framework.ViewModel
{
public class DefaultPropertySerialization : IPropertySerialization
{
public string ResolveName(PropertyInfo propertyInfo)
static readonly Type? JsonPropertyNJ = Type.GetType("Newtonsoft.Json.JsonPropertyAttribute, Newtonsoft.Json");
static readonly PropertyInfo? JsonPropertyNJPropertyName = JsonPropertyNJ?.GetProperty("PropertyName");
public string ResolveName(MemberInfo propertyInfo)
{
var bindAttribute = propertyInfo.GetCustomAttribute<BindAttribute>();
if (bindAttribute != null)
Expand All @@ -16,13 +19,23 @@ public string ResolveName(PropertyInfo propertyInfo)
}
}

if (string.IsNullOrEmpty(bindAttribute?.Name))
// use JsonPropertyName name if Bind attribute is not present or doesn't specify it
var jsonPropertyAttribute = propertyInfo.GetCustomAttribute<JsonPropertyNameAttribute>();
if (!string.IsNullOrEmpty(jsonPropertyAttribute?.Name))
{
// use JsonProperty name if Bind attribute is not present or doesn't specify it
var jsonPropertyAttribute = propertyInfo.GetCustomAttribute<JsonPropertyAttribute>();
if (!string.IsNullOrEmpty(jsonPropertyAttribute?.PropertyName))
return jsonPropertyAttribute!.Name!;
}

if (JsonPropertyNJ is not null)
{
var jsonPropertyNJAttribute = propertyInfo.GetCustomAttribute(JsonPropertyNJ);
if (jsonPropertyNJAttribute is not null)
{
return jsonPropertyAttribute!.PropertyName!;
var name = (string?)JsonPropertyNJPropertyName!.GetValue(jsonPropertyNJAttribute);
if (!string.IsNullOrEmpty(name))
{
return name;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Framework/Core/ViewModel/IPropertySerialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace DotVVM.Framework.ViewModel
{
public interface IPropertySerialization
{
string ResolveName(PropertyInfo propertyInfo);
string ResolveName(MemberInfo propertyInfo);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using DotVVM.Framework.Binding.Expressions;
using DotVVM.Framework.Compilation;
using DotVVM.Framework.Runtime;
using FastExpressionCompiler;
using Newtonsoft.Json;

namespace DotVVM.Framework.Binding
{
Expand Down
2 changes: 1 addition & 1 deletion src/Framework/Framework/Binding/DotvvmProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
using DotVVM.Framework.Binding.Expressions;
using DotVVM.Framework.Compilation.ControlTree;
using DotVVM.Framework.Compilation.ControlTree.Resolved;
using Newtonsoft.Json;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Immutable;
using DotVVM.Framework.Runtime;
using System.Threading;
using System.Text.Json.Serialization;

namespace DotVVM.Framework.Binding
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using DotVVM.Framework.ResourceManagement;

namespace DotVVM.Framework.Binding.Expressions
{
internal class BindingDebugJsonConverter: JsonConverter
{
public override bool CanConvert(Type objectType) =>
typeof(IBinding).IsAssignableFrom(objectType);
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) =>
throw new NotImplementedException("Deserializing dotvvm bindings from JSON is not supported.");
public override void WriteJson(JsonWriter w, object? valueObj, JsonSerializer serializer)
internal class BindingDebugJsonConverter(bool detailed): GenericWriterJsonConverter<IBinding>((writer, obj, options) => {
if (detailed)
{
var obj = valueObj;
w.WriteValue(obj?.ToString());

// w.WriteStartObject();
// w.WritePropertyName("ToString");
// w.WriteValue(obj.ToString());
// var props = (obj as ICloneableBinding)?.GetAllComputedProperties() ?? Enumerable.Empty<IBinding>();
// foreach (var p in props)
// {
// var name = p.GetType().Name;
// w.WritePropertyName(name);
// serializer.Serialize(w, p);
// }
// w.WriteEndObject();
writer.WriteStartObject();
writer.WriteString("ToString"u8, obj.ToString());
var props = (obj as ICloneableBinding)?.GetAllComputedProperties() ?? Enumerable.Empty<IBinding>();
foreach (var p in props)
{
var name = p.GetType().Name;
writer.WritePropertyName(name);
JsonSerializer.Serialize(writer, p, options);
}
writer.WriteEndObject();
}
else
{
writer.WriteStringValue(obj?.ToString());
}
})
{
public BindingDebugJsonConverter() : this(false) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace DotVVM.Framework.Binding.Expressions
/// This is a base class for all bindings, BindingExpression in general does not guarantee that the binding will have any property.
/// This class only contains the glue code which automatically calls resolvers and caches the results when <see cref="GetProperty(Type, ErrorHandlingMode)" /> is invoked. </summary>
[BindingCompilationRequirements(optional: new[] { typeof(BindingResolverCollection) })]
[Newtonsoft.Json.JsonConverter(typeof(BindingDebugJsonConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(BindingDebugJsonConverter))]
public abstract class BindingExpression : IBinding, ICloneableBinding
{
private protected struct PropValue<TValue> where TValue : class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using DotVVM.Framework.Binding.Properties;
using DotVVM.Framework.Compilation;
using DotVVM.Framework.Compilation.Binding;
using DotVVM.Framework.Compilation.Javascript;
using DotVVM.Framework.Compilation.Javascript.Ast;
using DotVVM.Framework.Controls;
using DotVVM.Framework.Runtime.Filters;
using DotVVM.Framework.Utils;
using FastExpressionCompiler;
using Newtonsoft.Json;

namespace DotVVM.Framework.Binding.Expressions
{
Expand Down Expand Up @@ -149,7 +144,7 @@ public static ParametrizedCode CreateJsPostbackInvocation(string id, bool? needs
needsCommandArgs == false ? javascriptPostbackInvocation_noCommandArgs :
javascriptPostbackInvocation)
.AssignParameters(p =>
p == CommandIdParameter ? CodeParameterAssignment.FromLiteral(id) :
p == CommandIdParameter ? new(KnockoutHelper.MakeStringLiteral(id, htmlSafe: false), OperatorPrecedence.Max) :
default);

public CommandBindingExpression(BindingCompilationService service, Action<object[]> command, string id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Text.RegularExpressions;
using DotVVM.Framework.Compilation.Javascript;
using DotVVM.Framework.Compilation.Javascript.Ast;
using Newtonsoft.Json;
using Generic = DotVVM.Framework.Compilation.Javascript.MethodFindingHelper.Generic;

namespace DotVVM.Framework.Binding.HelperNamespace
Expand Down
6 changes: 3 additions & 3 deletions src/Framework/Framework/Binding/ValueOrBinding.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using DotVVM.Framework.Binding.Expressions;
using DotVVM.Framework.Binding.Properties;
using DotVVM.Framework.Compilation.Javascript;
using DotVVM.Framework.Configuration;
using DotVVM.Framework.Controls;
using DotVVM.Framework.Utils;
using Newtonsoft.Json;

namespace DotVVM.Framework.Binding
{
Expand Down Expand Up @@ -111,14 +111,14 @@ public ValueOrBinding<T2> UpCast<T2>()
/// <summary> Returns a Javascript (knockout) expression representing this value or this binding. </summary>
public ParametrizedCode GetParametrizedJsExpression(DotvvmBindableObject control, bool unwrapped = false) =>
ProcessValueBinding(control,
value => new ParametrizedCode(JsonConvert.SerializeObject(value, DefaultSerializerSettingsProvider.Instance.Settings), OperatorPrecedence.Max),
value => new ParametrizedCode(JsonSerializer.Serialize(value, DefaultSerializerSettingsProvider.Instance.Settings), OperatorPrecedence.Max),
binding => binding.GetParametrizedKnockoutExpression(control, unwrapped)
);

/// <summary> Returns a Javascript (knockout) expression representing this value or this binding. The parameters are set to defaults, so knockout context is $context, view model is $data and both are available as global. </summary>
public string GetJsExpression(DotvvmBindableObject control, bool unwrapped = false) =>
ProcessValueBinding(control,
value => JsonConvert.SerializeObject(value, DefaultSerializerSettingsProvider.Instance.Settings),
value => JsonSerializer.Serialize(value, DefaultSerializerSettingsProvider.Instance.Settings),
binding => binding.GetKnockoutBindingExpression(control, unwrapped)
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
using DotVVM.Framework.Compilation.Javascript;
using DotVVM.Framework.Controls;
using DotVVM.Framework.Utils;
using Newtonsoft.Json;

public static class ValueOrBindingExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ public static Expression WrapAsTask(Expression expr)

}

public static Expression UnwrapNullable(this Expression expression) =>
expression.Type.IsNullable() ? Expression.Property(expression, "Value") : expression;
public static Expression UnwrapNullable(this Expression expression, bool throwOnNull = true) =>
!expression.Type.IsNullable() ? expression :
throwOnNull ? Expression.Property(expression, "Value") :
Expression.Call(expression, "GetValueOrDefault", Type.EmptyTypes);

public static Expression GetIndexer(Expression expr, Expression index)
{
Expand Down
Loading
Loading