diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 65cac6e7cd..6957eb2baf 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -355,6 +355,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Compone EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Swashbuckle", "src\Volo.Abp.Swashbuckle\Volo.Abp.Swashbuckle.csproj", "{DD9519E0-5A68-48DC-A051-7BF2AC922F3E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Json.Tests", "test\Volo.Abp.Json.Tests\Volo.Abp.Json.Tests.csproj", "{00D07595-993C-40FC-BD90-0DD6331414D3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1057,6 +1059,10 @@ Global {DD9519E0-5A68-48DC-A051-7BF2AC922F3E}.Debug|Any CPU.Build.0 = Debug|Any CPU {DD9519E0-5A68-48DC-A051-7BF2AC922F3E}.Release|Any CPU.ActiveCfg = Release|Any CPU {DD9519E0-5A68-48DC-A051-7BF2AC922F3E}.Release|Any CPU.Build.0 = Release|Any CPU + {00D07595-993C-40FC-BD90-0DD6331414D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00D07595-993C-40FC-BD90-0DD6331414D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00D07595-993C-40FC-BD90-0DD6331414D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00D07595-993C-40FC-BD90-0DD6331414D3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1236,6 +1242,7 @@ Global {B9D1ADCB-D552-4626-A1F1-78FF72C1E822} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {89840441-5A3A-4FD7-9CB4-E5B52FAEF72A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {DD9519E0-5A68-48DC-A051-7BF2AC922F3E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {00D07595-993C-40FC-BD90-0DD6331414D3} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj index 3e691f57f3..a018dbbffd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj @@ -28,8 +28,8 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 715ec0a0ce..61d73cb48b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -35,6 +35,7 @@ using Volo.Abp.Http; using Volo.Abp.DynamicProxy; using Volo.Abp.GlobalFeatures; using Volo.Abp.Http.Modeling; +using Volo.Abp.Json; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.UI; @@ -115,11 +116,6 @@ namespace Volo.Abp.AspNetCore.Mvc ); var mvcBuilder = context.Services.AddMvc() - .AddNewtonsoftJson(options => - { - options.SerializerSettings.ContractResolver = - new AbpMvcJsonContractResolver(context.Services); - }) .AddRazorRuntimeCompilation() .AddDataAnnotationsLocalization(options => { @@ -140,6 +136,8 @@ namespace Volo.Abp.AspNetCore.Mvc }) .AddViewLocalization(); //TODO: How to configure from the application? Also, consider to move to a UI module since APIs does not care about it. + mvcCoreBuilder.AddAbpHybridJson(); + Configure(options => { options.FileProviders.Add( diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpHybridJsonInputFormatter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpHybridJsonInputFormatter.cs new file mode 100644 index 0000000000..bd2ade69ad --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpHybridJsonInputFormatter.cs @@ -0,0 +1,39 @@ +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Json.SystemTextJson; + +namespace Volo.Abp.AspNetCore.Mvc.Json +{ + public class AbpHybridJsonInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy + { + public AbpHybridJsonInputFormatter() + { + SupportedEncodings.Add(UTF8EncodingWithoutBOM); + SupportedEncodings.Add(UTF16EncodingLittleEndian); + + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson); + SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson); + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax); + } + + public async override Task ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) + { + return await GetTextInputFormatter(context).ReadRequestBodyAsync(context, encoding); + } + + protected virtual TextInputFormatter GetTextInputFormatter(InputFormatterContext context) + { + var typesMatcher = context.HttpContext.RequestServices.GetRequiredService(); + if (!typesMatcher.Match(context.ModelType)) + { + return context.HttpContext.RequestServices.GetRequiredService(); + } + + return context.HttpContext.RequestServices.GetRequiredService(); + } + + public virtual InputFormatterExceptionPolicy ExceptionPolicy => InputFormatterExceptionPolicy.MalformedInputExceptions; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpHybridJsonOutputFormatter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpHybridJsonOutputFormatter.cs new file mode 100644 index 0000000000..1507457f08 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpHybridJsonOutputFormatter.cs @@ -0,0 +1,37 @@ +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Json.SystemTextJson; + +namespace Volo.Abp.AspNetCore.Mvc.Json +{ + public class AbpHybridJsonOutputFormatter : TextOutputFormatter + { + public AbpHybridJsonOutputFormatter() + { + SupportedEncodings.Add(Encoding.UTF8); + SupportedEncodings.Add(Encoding.Unicode); + + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson); + SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson); + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax); + } + + public async override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + await GetTextInputFormatter(context).WriteResponseBodyAsync(context, selectedEncoding); + } + + protected virtual TextOutputFormatter GetTextInputFormatter(OutputFormatterWriteContext context) + { + var typesMatcher = context.HttpContext.RequestServices.GetRequiredService(); + if (!typesMatcher.Match(context.ObjectType)) + { + return context.HttpContext.RequestServices.GetRequiredService(); + } + + return context.HttpContext.RequestServices.GetRequiredService(); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs new file mode 100644 index 0000000000..4b63828997 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs @@ -0,0 +1,28 @@ +using System; +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.Json.SystemTextJson.JsonConverters; + +namespace Volo.Abp.AspNetCore.Mvc.Json +{ + public class AbpJsonOptionsSetup : IConfigureOptions + { + protected IServiceProvider ServiceProvider { get; } + + public AbpJsonOptionsSetup(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public void Configure(JsonOptions options) + { + options.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip; + options.JsonSerializerOptions.AllowTrailingCommas = true; + + options.JsonSerializerOptions.Converters.Add(ServiceProvider.GetRequiredService()); + options.JsonSerializerOptions.Converters.Add(ServiceProvider.GetRequiredService()); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpMvcJsonContractResolver.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpMvcJsonContractResolver.cs index 695212f563..2021afd492 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpMvcJsonContractResolver.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpMvcJsonContractResolver.cs @@ -1,28 +1,32 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using System; -using System.Reflection; +using Volo.Abp.DependencyInjection; using Volo.Abp.Json.Newtonsoft; using Volo.Abp.Reflection; using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Mvc.Json { - public class AbpMvcJsonContractResolver : DefaultContractResolver + public class AbpMvcJsonContractResolver : DefaultContractResolver, ITransientDependency { private readonly Lazy _dateTimeConverter; - public AbpMvcJsonContractResolver(IServiceCollection services) + public AbpMvcJsonContractResolver(IServiceProvider serviceProvider) { - _dateTimeConverter = services.GetServiceLazy(); + _dateTimeConverter = new Lazy( + serviceProvider.GetRequiredService, + true + ); NamingStrategy = new CamelCaseNamingStrategy(); } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { - JsonProperty property = base.CreateProperty(member, memberSerialization); + var property = base.CreateProperty(member, memberSerialization); ModifyProperty(member, property); @@ -40,7 +44,6 @@ namespace Volo.Abp.AspNetCore.Mvc.Json { property.Converter = _dateTimeConverter.Value; } - } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpMvcNewtonsoftJsonOptionsSetup.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpMvcNewtonsoftJsonOptionsSetup.cs new file mode 100644 index 0000000000..48a09e2426 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpMvcNewtonsoftJsonOptionsSetup.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.AspNetCore.Mvc.Json +{ + public class AbpMvcNewtonsoftJsonOptionsSetup : IConfigureOptions + { + protected IServiceProvider ServiceProvider { get; } + + public AbpMvcNewtonsoftJsonOptionsSetup(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public void Configure(MvcNewtonsoftJsonOptions options) + { + options.SerializerSettings.ContractResolver = ServiceProvider.GetRequiredService(); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MediaTypeHeaderValues.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MediaTypeHeaderValues.cs new file mode 100644 index 0000000000..d1f1b34b1a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MediaTypeHeaderValues.cs @@ -0,0 +1,22 @@ +using Microsoft.Net.Http.Headers; + +namespace Volo.Abp.AspNetCore.Mvc.Json +{ + /// + /// https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.NewtonsoftJson/src/MediaTypeHeaderValues.cs + /// + internal static class MediaTypeHeaderValues + { + public static readonly MediaTypeHeaderValue ApplicationJson = MediaTypeHeaderValue.Parse("application/json").CopyAsReadOnly(); + + public static readonly MediaTypeHeaderValue TextJson = MediaTypeHeaderValue.Parse("text/json").CopyAsReadOnly(); + + public static readonly MediaTypeHeaderValue ApplicationAnyJsonSyntax = MediaTypeHeaderValue.Parse("application/*+json").CopyAsReadOnly(); + + public static readonly MediaTypeHeaderValue ApplicationXml = MediaTypeHeaderValue.Parse("application/xml").CopyAsReadOnly(); + + public static readonly MediaTypeHeaderValue TextXml = MediaTypeHeaderValue.Parse("text/xml").CopyAsReadOnly(); + + public static readonly MediaTypeHeaderValue ApplicationAnyXmlSyntax = MediaTypeHeaderValue.Parse("application/*+xml").CopyAsReadOnly(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs new file mode 100644 index 0000000000..5a01cafb22 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs @@ -0,0 +1,83 @@ +using System.Buffers; +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using System.Text.Encodings.Web; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; + +namespace Volo.Abp.AspNetCore.Mvc.Json +{ + public static class MvcCoreBuilderExtensions + { + public static IMvcCoreBuilder AddAbpHybridJson(this IMvcCoreBuilder builder) + { + //SystemTextJsonInputFormatter + builder.Services.AddTransient(provider => + { + var jsonOptions = provider.GetRequiredService>(); + var logger = provider.GetRequiredService().CreateLogger(); + return new SystemTextJsonInputFormatter(jsonOptions.Value, logger); + }); + + builder.Services.AddTransient(); + //NewtonsoftJsonInputFormatter + builder.Services.AddTransient(provider => + { + var jsonOptions = provider.GetRequiredService>().Value; + + return new NewtonsoftJsonInputFormatter( + provider.GetRequiredService().CreateLogger(), + jsonOptions.SerializerSettings, + provider.GetRequiredService>(), + provider.GetRequiredService(), + provider.GetRequiredService>().Value, + jsonOptions); + }); + + //SystemTextJsonOutputFormatter + builder.Services.AddTransient(provider => + { + var jsonSerializerOptions = provider.GetRequiredService>().Value.JsonSerializerOptions; + if (jsonSerializerOptions.Encoder is null) + { + // If the user hasn't explicitly configured the encoder, use the less strict encoder that does not encode all non-ASCII characters. + jsonSerializerOptions = new JsonSerializerOptions(jsonSerializerOptions) + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + } + return new SystemTextJsonOutputFormatter(jsonSerializerOptions); + }); + + //NewtonsoftJsonOutputFormatter + builder.Services.AddTransient(provider => + { + var jsonOptions = provider.GetRequiredService>().Value; + return new NewtonsoftJsonOutputFormatter( + jsonOptions.SerializerSettings, + provider.GetRequiredService>(), + provider.GetRequiredService>().Value); + }); + + builder.Services.TryAddEnumerable(ServiceDescriptor.Transient, AbpJsonOptionsSetup>()); + builder.Services.TryAddEnumerable(ServiceDescriptor.Transient, AbpMvcNewtonsoftJsonOptionsSetup>()); + + builder.Services.Configure(options => + { + options.InputFormatters.RemoveType(); + options.InputFormatters.RemoveType(); + options.InputFormatters.Add(new AbpHybridJsonInputFormatter()); + + options.OutputFormatters.RemoveType(); + options.OutputFormatters.RemoveType(); + options.OutputFormatters.Add(new AbpHybridJsonOutputFormatter()); + }); + + return builder; + } + } +} diff --git a/framework/src/Volo.Abp.Auditing/Volo.Abp.Auditing.csproj b/framework/src/Volo.Abp.Auditing/Volo.Abp.Auditing.csproj index 4ac2420c8f..2685236a89 100644 --- a/framework/src/Volo.Abp.Auditing/Volo.Abp.Auditing.csproj +++ b/framework/src/Volo.Abp.Auditing/Volo.Abp.Auditing.csproj @@ -23,4 +23,8 @@ + + + + diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingContractResolver.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingContractResolver.cs index e6d03bf707..a88de55aea 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingContractResolver.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingContractResolver.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -19,18 +20,21 @@ namespace Volo.Abp.Auditing { var property = base.CreateProperty(member, memberSerialization); - if (member.IsDefined(typeof(DisableAuditingAttribute)) || member.IsDefined(typeof(JsonIgnoreAttribute))) + if (_ignoredTypes.Any(ignoredType => ignoredType.GetTypeInfo().IsAssignableFrom(property.PropertyType))) { property.ShouldSerialize = instance => false; + return property; } - foreach (var ignoredType in _ignoredTypes) + if (member.DeclaringType != null && (member.DeclaringType.IsDefined(typeof(DisableAuditingAttribute)) || member.DeclaringType.IsDefined(typeof(JsonIgnoreAttribute)))) { - if (ignoredType.GetTypeInfo().IsAssignableFrom(property.PropertyType)) - { - property.ShouldSerialize = instance => false; - break; - } + property.ShouldSerialize = instance => false; + return property; + } + + if (member.IsDefined(typeof(DisableAuditingAttribute)) || member.IsDefined(typeof(JsonIgnoreAttribute))) + { + property.ShouldSerialize = instance => false; } return property; diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/JsonNetAuditSerializer.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/JsonNetAuditSerializer.cs index b86df6b6c8..b1ce95675b 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/JsonNetAuditSerializer.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/JsonNetAuditSerializer.cs @@ -41,4 +41,4 @@ namespace Volo.Abp.Auditing return _sharedJsonSerializerSettings; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs index 65f2992257..b778ec2b67 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs @@ -77,7 +77,7 @@ namespace Volo.Abp.Domain.Entities { var entity1Key = entity1Keys[i]; var entity2Key = entity2Keys[i]; - + if (entity1Key == null) { if (entity2Key == null) @@ -89,13 +89,13 @@ namespace Volo.Abp.Domain.Entities //entity2Key is not null! return false; } - + if (entity2Key == null) { //entity1Key was not null! return false; } - + if (TypeHelper.IsDefaultValue(entity1Key) && TypeHelper.IsDefaultValue(entity2Key)) { return false; @@ -266,5 +266,36 @@ namespace Volo.Abp.Domain.Entities property?.SetValue(entity, idFactory()); } + + public static void TrySetId( + object entity, + TKey id, + bool checkForDisableIdGenerationAttribute = false) + { + var property = CachedIdProperties.GetOrAdd( + $"{entity.GetType().FullName}-{checkForDisableIdGenerationAttribute}", () => + { + var idProperty = entity + .GetType() + .GetProperties() + .FirstOrDefault(x => x.Name == nameof(IEntity.Id) && + x.GetSetMethod(true) != null); + + if (idProperty == null) + { + return null; + } + + if (checkForDisableIdGenerationAttribute && + idProperty.IsDefined(typeof(DisableIdGenerationAttribute), true)) + { + return null; + } + + return idProperty; + }); + + property?.SetValue(entity, id); + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs index 6ecf19fa98..7378ab8eff 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs @@ -1,6 +1,6 @@ -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Newtonsoft.Json; -using Volo.Abp.Data; +using System.Text.Json; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.Json.SystemTextJson.JsonConverters; namespace Volo.Abp.EntityFrameworkCore.ValueConverters { @@ -16,12 +16,14 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters private static string SerializeObject(TPropertyType d) { - return JsonConvert.SerializeObject(d, Formatting.None); + return JsonSerializer.Serialize(d); } private static TPropertyType DeserializeObject(string s) { - return JsonConvert.DeserializeObject(s); + var deserializeOptions = new JsonSerializerOptions(); + deserializeOptions.Converters.Add(new ObjectToInferredTypesConverter()); + return JsonSerializer.Deserialize(s, deserializeOptions); } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs index 2fa00e3caf..661da95a19 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.ComponentModel; +using System.Text.Json; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Newtonsoft.Json; +using Volo.Abp.Json.SystemTextJson.JsonConverters; using Volo.Abp.ObjectExtending; namespace Volo.Abp.EntityFrameworkCore.ValueConverters @@ -36,13 +36,14 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters } } - return JsonConvert.SerializeObject(copyDictionary, Formatting.None); + return JsonSerializer.Serialize(copyDictionary); } private static Dictionary DeserializeObject(string extraPropertiesAsJson, Type entityType) { - var dictionary = JsonConvert.DeserializeObject>(extraPropertiesAsJson); - + var deserializeOptions = new JsonSerializerOptions(); + deserializeOptions.Converters.Add(new ObjectToInferredTypesConverter()); + var dictionary = JsonSerializer.Deserialize>(extraPropertiesAsJson, deserializeOptions) ?? new Dictionary(); if (entityType != null) { var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType); @@ -59,7 +60,7 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters } private static object GetNormalizedValue( - Dictionary dictionary, + Dictionary dictionary, ObjectExtensionPropertyInfo property) { var value = dictionary.GetOrDefault(property.Name); @@ -84,4 +85,4 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs index a9b51eca6a..9142118b97 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs @@ -2,9 +2,8 @@ using System.Linq; using System.Net.Http; using System.Reflection; +using System.Text.Json; using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Modeling; using Volo.Abp.Threading; @@ -17,11 +16,6 @@ namespace Volo.Abp.Http.Client.DynamicProxying protected IApiDescriptionCache Cache { get; } - private static readonly JsonSerializerSettings SharedJsonSerializerSettings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }; - public ApiDescriptionFinder(IApiDescriptionCache cache) { Cache = cache; @@ -91,9 +85,10 @@ namespace Volo.Abp.Http.Client.DynamicProxying var content = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject( - content, - typeof(ApplicationApiDescriptionModel), SharedJsonSerializerSettings); + var result = JsonSerializer.Deserialize(content, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); return (ApplicationApiDescriptionModel)result; } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs index 7e9d375d06..fffd9d14d5 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs @@ -26,7 +26,7 @@ namespace Volo.Abp.Http.Modeling public ReturnValueApiDescriptionModel ReturnValue { get; set; } - private ActionApiDescriptionModel() + public ActionApiDescriptionModel() { } @@ -71,4 +71,4 @@ namespace Volo.Abp.Http.Modeling return $"[ActionApiDescriptionModel {Name}]"; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs index cc09b7a153..77fb28eed5 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs @@ -12,9 +12,9 @@ namespace Volo.Abp.Http.Modeling public IDictionary Types { get; set; } - private ApplicationApiDescriptionModel() + public ApplicationApiDescriptionModel() { - + } public static ApplicationApiDescriptionModel Create() @@ -56,4 +56,4 @@ namespace Volo.Abp.Http.Modeling return subModel; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs index db5a5eb37a..3c4b1ee538 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs @@ -16,7 +16,7 @@ namespace Volo.Abp.Http.Modeling public Dictionary Actions { get; set; } - private ControllerApiDescriptionModel() + public ControllerApiDescriptionModel() { } @@ -79,4 +79,4 @@ namespace Volo.Abp.Http.Modeling return $"[ControllerApiDescriptionModel {ControllerName}]"; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs index 04b5ea1bc9..3515ee3129 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs @@ -7,9 +7,9 @@ namespace Volo.Abp.Http.Modeling { public string Type { get; set; } - private ControllerInterfaceApiDescriptionModel() + public ControllerInterfaceApiDescriptionModel() { - + } public static ControllerInterfaceApiDescriptionModel Create(Type type) @@ -20,4 +20,4 @@ namespace Volo.Abp.Http.Modeling }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs index b4e272c2f3..49e51fed17 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs @@ -19,7 +19,7 @@ namespace Volo.Abp.Http.Modeling public object DefaultValue { get; set; } - private MethodParameterApiDescriptionModel() + public MethodParameterApiDescriptionModel() { } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs index b879b90135..5c7c53745c 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs @@ -11,22 +11,22 @@ namespace Volo.Abp.Http.Modeling /// /// "app". /// - public const string DefaultRootPath = "app"; + public const string DefaultRootPath = "app"; /// /// "Default". /// - public const string DefaultRemoteServiceName = "Default"; - + public const string DefaultRemoteServiceName = "Default"; + public string RootPath { get; set; } public string RemoteServiceName { get; set; } public IDictionary Controllers { get; set; } - private ModuleApiDescriptionModel() + public ModuleApiDescriptionModel() { - + } public static ModuleApiDescriptionModel Create(string rootPath, string remoteServiceName) @@ -53,7 +53,7 @@ namespace Volo.Abp.Http.Modeling { return Controllers.GetOrAdd(uniqueName, () => ControllerApiDescriptionModel.Create(name, type, ignoredInterfaces)); } - + public ModuleApiDescriptionModel CreateSubModel(string[] controllers, string[] actions) { var subModel = Create(RootPath, RemoteServiceName); @@ -69,4 +69,4 @@ namespace Volo.Abp.Http.Modeling return subModel; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs index 675ba8bf54..8bdead565e 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs @@ -24,7 +24,7 @@ namespace Volo.Abp.Http.Modeling public string DescriptorName { get; set; } - private ParameterApiDescriptionModel() + public ParameterApiDescriptionModel() { } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs index 83fa4bb6be..291abf09ae 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs @@ -11,7 +11,7 @@ namespace Volo.Abp.Http.Modeling public string TypeSimple { get; set; } - private ReturnValueApiDescriptionModel() + public ReturnValueApiDescriptionModel() { } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs index 4b0b0b870c..6114ed2ee6 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs @@ -19,7 +19,7 @@ namespace Volo.Abp.Http.Modeling public PropertyApiDescriptionModel[] Properties { get; set; } - private TypeApiDescriptionModel() + public TypeApiDescriptionModel() { } diff --git a/framework/src/Volo.Abp.Json/Volo.Abp.Json.csproj b/framework/src/Volo.Abp.Json/Volo.Abp.Json.csproj index 4e891a687d..1ef71853fa 100644 --- a/framework/src/Volo.Abp.Json/Volo.Abp.Json.csproj +++ b/framework/src/Volo.Abp.Json/Volo.Abp.Json.csproj @@ -14,12 +14,9 @@ - - - - + diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpHybridJsonSerializer.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpHybridJsonSerializer.cs new file mode 100644 index 0000000000..560d6baa36 --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpHybridJsonSerializer.cs @@ -0,0 +1,62 @@ +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Json +{ + public class AbpHybridJsonSerializer : IJsonSerializer, ITransientDependency + { + protected AbpJsonOptions Options { get; } + + protected IServiceScopeFactory ServiceScopeFactory { get; } + + public AbpHybridJsonSerializer(IOptions options, IServiceScopeFactory serviceScopeFactory) + { + Options = options.Value; + ServiceScopeFactory = serviceScopeFactory; + } + + public string Serialize(object obj, bool camelCase = true, bool indented = false) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var serializerProvider = GetSerializerProvider(scope.ServiceProvider, obj.GetType()); + return serializerProvider.Serialize(obj, camelCase, indented); + } + } + + public T Deserialize(string jsonString, bool camelCase = true) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var serializerProvider = GetSerializerProvider(scope.ServiceProvider, typeof(T)); + return serializerProvider.Deserialize(jsonString, camelCase); + } + } + + public object Deserialize(Type type, string jsonString, bool camelCase = true) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var serializerProvider = GetSerializerProvider(scope.ServiceProvider, type); + return serializerProvider.Deserialize(type, jsonString, camelCase); + } + } + + protected virtual IJsonSerializerProvider GetSerializerProvider(IServiceProvider serviceProvider, Type type) + { + foreach (var providerType in Options.Providers.Reverse()) + { + var provider = serviceProvider.GetRequiredService(providerType) as IJsonSerializerProvider; + if (provider.CanHandle(type)) + { + return provider; + } + } + + throw new AbpException($"There is no IJsonSerializerProvider that can handle '{type.GetFullNameWithAssemblyName()}'!"); + } + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs index c3ea13da43..fae54ec08b 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs @@ -1,4 +1,8 @@ -using Volo.Abp.Json.Newtonsoft; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Volo.Abp.Json.Newtonsoft; +using Volo.Abp.Json.SystemTextJson; using Volo.Abp.Modularity; using Volo.Abp.Timing; @@ -9,6 +13,15 @@ namespace Volo.Abp.Json { public override void ConfigureServices(ServiceConfigurationContext context) { + context.Services.TryAddEnumerable(ServiceDescriptor + .Transient, AbpSystemTextJsonSerializerOptionsSetup>()); + + Configure(options => + { + options.Providers.Add(); + options.Providers.Add(); + }); + Configure(options => { options.Converters.Add(); diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonOptions.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonOptions.cs index 9449aa338d..55a4683e26 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonOptions.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonOptions.cs @@ -1,4 +1,6 @@ -namespace Volo.Abp.Json +using Volo.Abp.Collections; + +namespace Volo.Abp.Json { public class AbpJsonOptions { @@ -6,5 +8,12 @@ /// Used to set default value for the DateTimeFormat. /// public string DefaultDateTimeFormat { get; set; } + + public ITypeList Providers { get; } + + public AbpJsonOptions() + { + Providers = new TypeList(); + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/IJsonSerializerProvider.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/IJsonSerializerProvider.cs new file mode 100644 index 0000000000..f844dc5fd0 --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/IJsonSerializerProvider.cs @@ -0,0 +1,15 @@ +using System; + +namespace Volo.Abp.Json +{ + public interface IJsonSerializerProvider + { + bool CanHandle(Type type); + + string Serialize(object obj, bool camelCase = true, bool indented = false); + + T Deserialize(string jsonString, bool camelCase = true); + + object Deserialize(Type type, string jsonString, bool camelCase = true); + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/NewtonsoftJsonSerializer.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializerProvider.cs similarity index 91% rename from framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/NewtonsoftJsonSerializer.cs rename to framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializerProvider.cs index 20e49ac817..2d288459cf 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/NewtonsoftJsonSerializer.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializerProvider.cs @@ -9,14 +9,14 @@ using Volo.Abp.DependencyInjection; namespace Volo.Abp.Json.Newtonsoft { - public class NewtonsoftJsonSerializer : IJsonSerializer, ITransientDependency + public class AbpNewtonsoftJsonSerializerProvider : IJsonSerializerProvider, ITransientDependency { private static readonly CamelCaseExceptDictionaryKeysResolver SharedCamelCaseExceptDictionaryKeysResolver = new CamelCaseExceptDictionaryKeysResolver(); protected List Converters { get; } - public NewtonsoftJsonSerializer( + public AbpNewtonsoftJsonSerializerProvider( IOptions options, IServiceProvider serviceProvider) { @@ -26,6 +26,11 @@ namespace Volo.Abp.Json.Newtonsoft .ToList(); } + public bool CanHandle(Type type) + { + return true; + } + public string Serialize(object obj, bool camelCase = true, bool indented = false) { return JsonConvert.SerializeObject(obj, CreateSerializerSettings(camelCase, indented)); diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptions.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptions.cs new file mode 100644 index 0000000000..e2774df58a --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptions.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using Volo.Abp.Collections; + +namespace Volo.Abp.Json.SystemTextJson +{ + public class AbpSystemTextJsonSerializerOptions + { + public JsonSerializerOptions JsonSerializerOptions { get; } + + public ITypeList UnsupportedTypes { get; } + + public AbpSystemTextJsonSerializerOptions() + { + JsonSerializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web) + { + ReadCommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true + }; + + UnsupportedTypes = new TypeList(); + } + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs new file mode 100644 index 0000000000..4b56997d47 --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.Json.SystemTextJson; +using Volo.Abp.Json.SystemTextJson.JsonConverters; + +namespace Volo.Abp.Json +{ + public class AbpSystemTextJsonSerializerOptionsSetup : IConfigureOptions + { + protected IServiceProvider ServiceProvider { get; } + + public AbpSystemTextJsonSerializerOptionsSetup(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public void Configure(AbpSystemTextJsonSerializerOptions options) + { + options.JsonSerializerOptions.Converters.Add(ServiceProvider.GetRequiredService()); + options.JsonSerializerOptions.Converters.Add(ServiceProvider.GetRequiredService()); + } + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerProvider.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerProvider.cs new file mode 100644 index 0000000000..570d1c162c --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerProvider.cs @@ -0,0 +1,59 @@ +using System; +using System.Text.Json; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Json.SystemTextJson +{ + public class AbpSystemTextJsonSerializerProvider : IJsonSerializerProvider, ITransientDependency + { + protected AbpSystemTextJsonSerializerOptions Options { get; } + + protected AbpSystemTextJsonUnsupportedTypeMatcher AbpSystemTextJsonUnsupportedTypeMatcher { get; } + + public AbpSystemTextJsonSerializerProvider( + IOptions options, + AbpSystemTextJsonUnsupportedTypeMatcher abpSystemTextJsonUnsupportedTypeMatcher) + { + AbpSystemTextJsonUnsupportedTypeMatcher = abpSystemTextJsonUnsupportedTypeMatcher; + Options = options.Value; + } + + public bool CanHandle(Type type) + { + return !AbpSystemTextJsonUnsupportedTypeMatcher.Match(type); + } + + public string Serialize(object obj, bool camelCase = true, bool indented = false) + { + return JsonSerializer.Serialize(obj, CreateJsonSerializerOptions(camelCase, indented)); + } + + public T Deserialize(string jsonString, bool camelCase = true) + { + return JsonSerializer.Deserialize(jsonString, CreateJsonSerializerOptions(camelCase)); + } + + public object Deserialize(Type type, string jsonString, bool camelCase = true) + { + return JsonSerializer.Deserialize(jsonString, type, CreateJsonSerializerOptions(camelCase)); + } + + protected virtual JsonSerializerOptions CreateJsonSerializerOptions(bool camelCase = true, bool indented = false) + { + var settings = new JsonSerializerOptions(Options.JsonSerializerOptions); + + if (camelCase) + { + settings.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + } + + if (indented) + { + settings.WriteIndented = true; + } + + return settings; + } + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonUnsupportedTypeMatcher.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonUnsupportedTypeMatcher.cs new file mode 100644 index 0000000000..2beea42c68 --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonUnsupportedTypeMatcher.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Json.SystemTextJson +{ + public class AbpSystemTextJsonUnsupportedTypeMatcher : ITransientDependency + { + protected AbpSystemTextJsonSerializerOptions Options { get; } + + public AbpSystemTextJsonUnsupportedTypeMatcher(IOptions options) + { + Options = options.Value; + } + + public virtual bool Match(Type type) + { + return Options.UnsupportedTypes.Contains(type); + } + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs new file mode 100644 index 0000000000..a87add029e --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs @@ -0,0 +1,38 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Timing; + +namespace Volo.Abp.Json.SystemTextJson.JsonConverters +{ + public class AbpDateTimeConverter : JsonConverter, ITransientDependency + { + private readonly IClock _clock; + private readonly AbpJsonOptions _options; + + public AbpDateTimeConverter(IClock clock, IOptions abpJsonOptions) + { + _clock = clock; + _options = abpJsonOptions.Value; + } + + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return _clock.Normalize(reader.GetDateTime()); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + if (_options.DefaultDateTimeFormat.IsNullOrWhiteSpace()) + { + writer.WriteStringValue(_clock.Normalize(value)); + } + else + { + writer.WriteStringValue(_clock.Normalize(value).ToString(_options.DefaultDateTimeFormat)); + } + } + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs new file mode 100644 index 0000000000..bad650b4d8 --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs @@ -0,0 +1,50 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Timing; + +namespace Volo.Abp.Json.SystemTextJson.JsonConverters +{ + public class AbpNullableDateTimeConverter : JsonConverter, ITransientDependency + { + private readonly IClock _clock; + private readonly AbpJsonOptions _options; + + public AbpNullableDateTimeConverter(IClock clock, IOptions abpJsonOptions) + { + _clock = clock; + _options = abpJsonOptions.Value; + } + + public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TryGetDateTime(out var dateTime)) + { + return _clock.Normalize(dateTime); + } + + return null; + } + + public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + } + else + { + if (_options.DefaultDateTimeFormat.IsNullOrWhiteSpace()) + { + writer.WriteStringValue(_clock.Normalize(value.Value)); + } + else + { + writer.WriteStringValue(_clock.Normalize(value.Value).ToString(_options.DefaultDateTimeFormat)); + } + } + } + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs new file mode 100644 index 0000000000..02fa9bbaff --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/JsonConverters/ObjectToInferredTypesConverter.cs @@ -0,0 +1,57 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Volo.Abp.Json.SystemTextJson.JsonConverters +{ + /// + /// https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#deserialize-inferred-types-to-object-properties + /// + public class ObjectToInferredTypesConverter : JsonConverter + { + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.True) + { + return true; + } + + if (reader.TokenType == JsonTokenType.False) + { + return false; + } + + if (reader.TokenType == JsonTokenType.Number) + { + if (reader.TryGetInt64(out var l)) + { + return l; + } + + return reader.GetDouble(); + } + + if (reader.TokenType == JsonTokenType.String) + { + if (reader.TryGetDateTime(out var datetime)) + { + return datetime; + } + + return reader.GetString(); + } + + // Use JsonElement as fallback. + // Newtonsoft uses JArray or JObject. + using (var document = JsonDocument.ParseValue(ref reader)) + { + return document.RootElement.Clone(); + } + } + + public override void Write(Utf8JsonWriter writer, object objectToWrite, JsonSerializerOptions options) + { + throw new InvalidOperationException("Should not get here."); + } + } +} diff --git a/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj b/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj index df04ab95df..64352257f3 100644 --- a/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj +++ b/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj @@ -19,10 +19,6 @@ - - - - diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs index 9b1f1617f6..9ccc6738a6 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs @@ -1,19 +1,13 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.Json; using Microsoft.Extensions.Localization; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; namespace Volo.Abp.Localization.Json { public static class JsonLocalizationDictionaryBuilder { - private static readonly JsonSerializerSettings SharedJsonSerializerSettings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }; - /// /// Builds an from given file. /// @@ -39,8 +33,15 @@ namespace Volo.Abp.Localization.Json JsonLocalizationFile jsonFile; try { - jsonFile = JsonConvert.DeserializeObject( - jsonString, SharedJsonSerializerSettings); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + ReadCommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true + }; + + jsonFile = JsonSerializer.Deserialize(jsonString, options); } catch (JsonException ex) { @@ -80,4 +81,4 @@ namespace Volo.Abp.Localization.Json return new StaticLocalizationDictionary(cultureCode, dictionary); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationFile.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationFile.cs index c2dc911532..2af738221a 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationFile.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationFile.cs @@ -9,11 +9,11 @@ namespace Volo.Abp.Localization.Json /// public string Culture { get; set; } - public Dictionary Texts { get; } + public Dictionary Texts { get; set; } public JsonLocalizationFile() { Texts = new Dictionary(); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/Utf8JsonMemoryDbSerializer.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/Utf8JsonMemoryDbSerializer.cs index e68b4b6f37..e0040a678d 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/Utf8JsonMemoryDbSerializer.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/Utf8JsonMemoryDbSerializer.cs @@ -1,52 +1,27 @@ using System; -using System.Reflection; -using System.Text; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; +using System.Text.Json; +using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; -using Volo.Abp.Json; namespace Volo.Abp.Domain.Repositories.MemoryDb { public class Utf8JsonMemoryDbSerializer : IMemoryDbSerializer, ITransientDependency { - private static readonly JsonSerializerSettings MemoryDbSerializerSettings; + protected Utf8JsonMemoryDbSerializerOptions Options { get; } - static Utf8JsonMemoryDbSerializer() + public Utf8JsonMemoryDbSerializer(IOptions options) { - MemoryDbSerializerSettings = new JsonSerializerSettings - { - ContractResolver = new ResolverWithPrivateSetters(), - ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor - }; + Options = options.Value; } byte[] IMemoryDbSerializer.Serialize(object obj) { - var jsonString = JsonConvert.SerializeObject(obj, MemoryDbSerializerSettings); - return Encoding.UTF8.GetBytes(jsonString); + return JsonSerializer.SerializeToUtf8Bytes(obj, Options.JsonSerializerOptions); } public object Deserialize(byte[] value, Type type) { - var jsonString = Encoding.UTF8.GetString(value); - return JsonConvert.DeserializeObject(jsonString, type, MemoryDbSerializerSettings); - } - - public class ResolverWithPrivateSetters : DefaultContractResolver - { - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - var prop = base.CreateProperty(member, memberSerialization); - if (prop.Writable) - { - return prop; - } - - prop.Writable = member.As()?.GetSetMethod(true) != null; - - return prop; - } + return JsonSerializer.Deserialize(value, type, Options.JsonSerializerOptions); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/Utf8JsonMemoryDbSerializerOptions.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/Utf8JsonMemoryDbSerializerOptions.cs new file mode 100644 index 0000000000..a31e9d5eff --- /dev/null +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/Utf8JsonMemoryDbSerializerOptions.cs @@ -0,0 +1,14 @@ +using System.Text.Json; + +namespace Volo.Abp.Domain.Repositories.MemoryDb +{ + public class Utf8JsonMemoryDbSerializerOptions + { + public JsonSerializerOptions JsonSerializerOptions { get; } + + public Utf8JsonMemoryDbSerializerOptions() + { + JsonSerializerOptions = new JsonSerializerOptions(); + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction_Tests.cs index bf869fc809..dd53e82167 100644 --- a/framework/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction_Tests.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Security.Claims; using System.Text.Json; -using Newtonsoft.Json.Linq; using Shouldly; using Volo.Abp.Security.Claims; using Xunit; diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs index 2c5a69e7e7..eea25417fc 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController.cs @@ -39,6 +39,16 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding input.Time3.Value.Kind.ToString().ToLower() + "_" + input.InnerModel.Time4.Kind.ToString().ToLower(); } + + //JSON input and output. + [HttpPost("ComplexTypeDateTimeKind_JSON")] + public string ComplexTypeDateTimeKind_JSON([FromBody]GetDateTimeKindModel input) + { + return input.Time1.Kind.ToString().ToLower() + "_" + + input.Time2.Kind.ToString().ToLower() + "_" + + input.Time3.Value.Kind.ToString().ToLower() + "_" + + input.InnerModel.Time4.Kind.ToString().ToLower(); + } } public class GetDateTimeKindModel diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs index 698723a34a..fcce316772 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ModelBinding/ModelBindingController_Tests.cs @@ -1,9 +1,14 @@ using System; using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Shouldly; +using Volo.Abp.Http; +using Volo.Abp.Json.SystemTextJson; using Volo.Abp.Timing; using Xunit; @@ -13,6 +18,15 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding { protected DateTimeKind DateTimeKind { get; set; } + protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + services.Configure(options => + { + options.UnsupportedTypes.Add(); + options.UnsupportedTypes.Add(); + }); + } + [Fact] public async Task DateTimeKind_Test() { @@ -75,8 +89,29 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding var resultAsString = await response.Content.ReadAsStringAsync(); //Time parameter(2010-01-01T00:00:00Z) with time zone information, so the default Kind is UTC //https://docs.microsoft.com/en-us/aspnet/core/migration/31-to-50?view=aspnetcore-3.1&tabs=visual-studio#datetime-values-are-model-bound-as-utc-times - resultAsString.ShouldBe( - $"utc_{DateTimeKind.ToString().ToLower()}_{DateTimeKind.ToString().ToLower()}_utc"); + resultAsString.ShouldBe($"utc_{DateTimeKind.ToString().ToLower()}_{DateTimeKind.ToString().ToLower()}_utc"); + } + + [Fact] + public async Task ComplexTypeDateTimeKind_JSON_Test() + { + var time = DateTime.Parse("2010-01-01T00:00:00Z"); + var response = await Client.PostAsync("/api/model-Binding-test/ComplexTypeDateTimeKind_JSON", + new StringContent(JsonSerializer.Serialize( + new GetDateTimeKindModel { + Time1 = time, + Time2 = time, + Time3 = time, + InnerModel = new GetDateTimeKindModel.GetDateTimeKindInnerModel + { + Time4 = time + } + } + ), Encoding.UTF8, MimeTypes.Application.Json)); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + var resultAsString = await response.Content.ReadAsStringAsync(); + resultAsString.ShouldBe($"local_{DateTimeKind.ToString().ToLower()}_{DateTimeKind.ToString().ToLower()}_local"); } } @@ -86,6 +121,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding { DateTimeKind = DateTimeKind.Utc; services.Configure(x => x.Kind = DateTimeKind); + + base.ConfigureServices(context, services); } } @@ -95,6 +132,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ModelBinding { DateTimeKind = DateTimeKind.Local; services.Configure(x => x.Kind = DateTimeKind); + + base.ConfigureServices(context, services); } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs b/framework/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs index dd5edff807..3c5571f115 100644 --- a/framework/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs +++ b/framework/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs @@ -1,10 +1,8 @@ using System.Globalization; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; +using System.Text.Json; using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using Shouldly; using Volo.Abp.AspNetCore.TestBase; @@ -18,16 +16,10 @@ namespace Volo.Abp.AspNetCore public abstract class AbpAspNetCoreTestBase : AbpAspNetCoreIntegratedTestBase where TStartup : class { - private static readonly JsonSerializerSettings SharedJsonSerializerSettings = - new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }; - protected virtual async Task GetResponseAsObjectAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) { var strResponse = await GetResponseAsStringAsync(url, expectedStatusCode); - return JsonConvert.DeserializeObject(strResponse, SharedJsonSerializerSettings); + return JsonSerializer.Deserialize(strResponse, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } protected virtual async Task GetResponseAsStringAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) @@ -49,4 +41,4 @@ namespace Volo.Abp.AspNetCore } } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/JsonNetAuditSerializer_Test.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/JsonNetAuditSerializer_Test.cs new file mode 100644 index 0000000000..da98f49bd6 --- /dev/null +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/JsonNetAuditSerializer_Test.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Auditing +{ + public class JsonNetAuditSerializer_Test : AbpAuditingTestBase + { + private readonly JsonNetAuditSerializer _jsonNetAuditSerializer; + + public JsonNetAuditSerializer_Test() + { + _jsonNetAuditSerializer = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.IgnoredTypes.Add(typeof(DateTime)); + }); + + base.AfterAddApplication(services); + } + + [Fact] + public void Serialize_Test() + { + var arguments = new Dictionary + { + {"input", new InputDto {PersonData = "IdCard:123123"}}, + {"input2", new Input2Dto {UserName = "admin", Password = "1q2w3E*", Birthday = DateTime.Now}} + }; + + var str = _jsonNetAuditSerializer.Serialize(arguments); + + str.ShouldNotContain("IdCard"); + str.ShouldNotContain("1q2w3E*"); + str.ShouldNotContain("Birthday"); + + str.ShouldContain("UserName"); + str.ShouldContain("admin"); + } + + + [DisableAuditing] + class InputDto + { + public string PersonData { get; set; } + } + + class Input2Dto + { + public string UserName { get; set; } + + [DisableAuditing] + public string Password { get; set; } + + [Newtonsoft.Json.JsonIgnore] + public string PrivateEmail { get; set; } + + public DateTime Birthday { get; set; } + } + } +} diff --git a/framework/test/Volo.Abp.Json.Tests/Volo.Abp.Json.Tests.csproj b/framework/test/Volo.Abp.Json.Tests/Volo.Abp.Json.Tests.csproj new file mode 100644 index 0000000000..fbc56e6ac2 --- /dev/null +++ b/framework/test/Volo.Abp.Json.Tests/Volo.Abp.Json.Tests.csproj @@ -0,0 +1,16 @@ + + + + + + net5.0 + + + + + + + + + + diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpHybridJsonSerializer_Tests.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpHybridJsonSerializer_Tests.cs new file mode 100644 index 0000000000..98f8acc7b4 --- /dev/null +++ b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpHybridJsonSerializer_Tests.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text.Json; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Shouldly; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json.Newtonsoft; +using Volo.Abp.Json.SystemTextJson; +using Xunit; +using JsonSerializer = Newtonsoft.Json.JsonSerializer; + +namespace Volo.Abp.Json +{ + public class AbpHybridJsonSerializer_Tests : AbpJsonTestBase + { + private readonly IJsonSerializer _jsonSerializer; + + public AbpHybridJsonSerializer_Tests() + { + _jsonSerializer = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.UnsupportedTypes.Add(); + + options.JsonSerializerOptions.Converters.Add(new SystemTextJsonConverter()); + }); + + services.Configure(options => + { + options.Converters.Add(); + }); + } + + [Fact] + public void NewtonsoftSerialize_Test() + { + var json = _jsonSerializer.Serialize(new MyClass1 + { + Providers = new List + { + new MyClass3() + } + }); + + json.ShouldContain("Newtonsoft"); + } + + [Fact] + public void SystemTextJsonSerialize_Test() + { + var json = _jsonSerializer.Serialize(new MyClass2 + { + Providers = new List + { + new MyClass3() + } + }); + + json.ShouldContain("SystemTextJson"); + } + + public class MyClass1 + { + public string Provider { get; set; } + + public List Providers { get; set; } + } + + public class MyClass2 + { + public string Provider { get; set; } + + public List Providers { get; set; } + } + + public class MyClass3 + { + public string Provider { get; set; } + } + + class NewtonsoftJsonConverter : JsonConverter, ITransientDependency + { + public override void WriteJson(JsonWriter writer, MyClass1 value, JsonSerializer serializer) + { + value.Provider = "Newtonsoft"; + foreach (var provider in value.Providers) + { + provider.Provider = "Newtonsoft"; + } + + writer.WriteRawValue(JsonConvert.SerializeObject(value)); + } + + public override MyClass1 ReadJson(JsonReader reader, Type objectType, MyClass1 existingValue, bool hasExistingValue, JsonSerializer serializer) + { + return (MyClass1)serializer.Deserialize(reader, objectType); + } + } + + class SystemTextJsonConverter : System.Text.Json.Serialization.JsonConverter, ITransientDependency + { + public override void Write(Utf8JsonWriter writer, MyClass2 value, JsonSerializerOptions options) + { + value.Provider = "SystemTextJson"; + foreach (var provider in value.Providers) + { + provider.Provider = "SystemTextJson"; + } + System.Text.Json.JsonSerializer.Serialize(writer, value); + } + + public override MyClass2 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return (MyClass2)System.Text.Json.JsonSerializer.Deserialize(ref reader, typeToConvert); + } + } + } +} diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestBase.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestBase.cs new file mode 100644 index 0000000000..5c9065ad80 --- /dev/null +++ b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestBase.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.Json +{ + public abstract class AbpJsonTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestModule.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestModule.cs new file mode 100644 index 0000000000..ddbc08ac3f --- /dev/null +++ b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestModule.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Json +{ + [DependsOn( + typeof(AbpAutofacModule), + typeof(AbpJsonModule), + typeof(AbpTestBaseModule) + )] + public class AbpJsonTestModule : AbpModule + { + + } +} diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonUnsupportedTypeMatcher_Tests.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonUnsupportedTypeMatcher_Tests.cs new file mode 100644 index 0000000000..cc31ceca34 --- /dev/null +++ b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpSystemTextJsonUnsupportedTypeMatcher_Tests.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Json.SystemTextJson; +using Xunit; + +namespace Volo.Abp.Json +{ + public class AbpSystemTextJsonUnsupportedTypeMatcher_Tests : AbpJsonTestBase + { + private readonly AbpSystemTextJsonUnsupportedTypeMatcher _abpSystemTextJsonUnsupportedTypeMatcher; + + public AbpSystemTextJsonUnsupportedTypeMatcher_Tests() + { + _abpSystemTextJsonUnsupportedTypeMatcher = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.UnsupportedTypes.Add(); + options.UnsupportedTypes.Add(); + options.UnsupportedTypes.Add>(); + }); + } + + [Fact] + public void Match_Test() + { + _abpSystemTextJsonUnsupportedTypeMatcher.Match(typeof(MyClass)).ShouldBeTrue(); + _abpSystemTextJsonUnsupportedTypeMatcher.Match(typeof(byte[])).ShouldBeTrue(); + + _abpSystemTextJsonUnsupportedTypeMatcher.Match(typeof(MyClass2)).ShouldBeFalse(); + _abpSystemTextJsonUnsupportedTypeMatcher.Match(typeof(MyClass3)).ShouldBeFalse(); + _abpSystemTextJsonUnsupportedTypeMatcher.Match(typeof(MyClass4)).ShouldBeFalse(); + + _abpSystemTextJsonUnsupportedTypeMatcher.Match(typeof(string)).ShouldBeFalse(); + _abpSystemTextJsonUnsupportedTypeMatcher.Match(typeof(string[])).ShouldBeFalse(); + + _abpSystemTextJsonUnsupportedTypeMatcher.Match(typeof(Dictionary)).ShouldBeTrue(); + _abpSystemTextJsonUnsupportedTypeMatcher.Match(typeof(IDictionary)).ShouldBeFalse(); + } + + class MyClass + { + public DateTime Prop1 { get; set; } + } + + class MyClass2 + { + public DateTime Prop1 { get; set; } + } + + class MyClass3 + { + public MyClass4 Prop1 { get; set; } + } + + class MyClass4 + { + public DateTime Prop1 { get; set; } + } + } +} diff --git a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/AbpMemoryDbTestModule.cs b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/AbpMemoryDbTestModule.cs index 7e5d9fcb6b..6f5d7a3718 100644 --- a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/AbpMemoryDbTestModule.cs +++ b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/AbpMemoryDbTestModule.cs @@ -4,14 +4,17 @@ using Volo.Abp.Modularity; using Volo.Abp.TestApp.MemoryDb; using Volo.Abp.Data; using Volo.Abp.Autofac; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Repositories.MemoryDb; +using Volo.Abp.MemoryDb.JsonConverters; using Volo.Abp.TestApp; using Volo.Abp.TestApp.Domain; namespace Volo.Abp.MemoryDb { [DependsOn( - typeof(TestAppModule), - typeof(AbpMemoryDbModule), + typeof(TestAppModule), + typeof(AbpMemoryDbModule), typeof(AbpAutofacModule))] public class AbpMemoryDbTestModule : AbpModule { @@ -29,6 +32,11 @@ namespace Volo.Abp.MemoryDb options.AddDefaultRepositories(); options.AddRepository(); }); + + Configure(options => + { + options.JsonSerializerOptions.Converters.Add(new EntityJsonConverter()); + }); } } } diff --git a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/JsonConverters/EntityJsonConverter.cs b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/JsonConverters/EntityJsonConverter.cs new file mode 100644 index 0000000000..8723ce8958 --- /dev/null +++ b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/JsonConverters/EntityJsonConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.MemoryDb.JsonConverters +{ + public class EntityJsonConverter : JsonConverter + { + public override TEntity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var jsonDocument = JsonDocument.ParseValue(ref reader); + var entity = (TEntity)JsonSerializer.Deserialize(jsonDocument.RootElement.GetRawText(), typeToConvert); + var idJsonElement = jsonDocument.RootElement.GetProperty(nameof(IEntity.Id)); + + var id = JsonSerializer.Deserialize(idJsonElement.GetRawText()); + if (id != null) + { + EntityHelper.TrySetId(entity, id); + } + + return entity; + } + + public override void Write(Utf8JsonWriter writer, TEntity value, JsonSerializerOptions options) + { + var newOptions = new JsonSerializerOptions(options); + newOptions.Converters.Remove(this); + var entityConverter = (JsonConverter)newOptions.GetConverter(typeof(TEntity)); + entityConverter.Write(writer, value, newOptions); + } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests_With_Int_Pk.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests_With_Int_Pk.cs index f873fe4265..821a80492b 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests_With_Int_Pk.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests_With_Int_Pk.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Domain.Repositories; diff --git a/modules/docs/src/Volo.Docs.Domain.Shared/Volo.Docs.Domain.Shared.csproj b/modules/docs/src/Volo.Docs.Domain.Shared/Volo.Docs.Domain.Shared.csproj index 99066c0b9b..d655e05df0 100644 --- a/modules/docs/src/Volo.Docs.Domain.Shared/Volo.Docs.Domain.Shared.csproj +++ b/modules/docs/src/Volo.Docs.Domain.Shared/Volo.Docs.Domain.Shared.csproj @@ -13,5 +13,9 @@ + + + + diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs index 37b49f2d8a..32939a2430 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/AbpFeatureManagementApplicationContractsModule.cs @@ -1,5 +1,8 @@ -using Volo.Abp.Application; -using Volo.Abp.Json.Newtonsoft; +using System.Collections.Generic; +using Volo.Abp.Application; +using Volo.Abp.FeatureManagement.JsonConverters; +using Volo.Abp.Json; +using Volo.Abp.Json.SystemTextJson; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -7,7 +10,8 @@ namespace Volo.Abp.FeatureManagement { [DependsOn( typeof(AbpFeatureManagementDomainSharedModule), - typeof(AbpDddApplicationModule) + typeof(AbpDddApplicationModule), + typeof(AbpJsonModule) )] public class AbpFeatureManagementApplicationContractsModule : AbpModule { @@ -18,9 +22,9 @@ namespace Volo.Abp.FeatureManagement options.FileSets.AddEmbedded(); }); - Configure(options => + Configure(options => { - options.Converters.Add(); + options.JsonSerializerOptions.Converters.AddIfNotContains(new StringValueTypeJsonConverter()); }); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/JsonSerializerOptionsHelper.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/JsonSerializerOptionsHelper.cs new file mode 100644 index 0000000000..26927f59fe --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/JsonSerializerOptionsHelper.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Volo.Abp.FeatureManagement.JsonConverters +{ + internal static class JsonSerializerOptionsHelper + { + public static JsonSerializerOptions Create(JsonSerializerOptions baseOptions, JsonConverter removeConverter, params JsonConverter[] addConverters) + { + var options = new JsonSerializerOptions(baseOptions); + options.Converters.RemoveAll(x => x == removeConverter); + options.Converters.AddIfNotContains(addConverters); + return options; + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs new file mode 100644 index 0000000000..feaa68ccc6 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/SelectionStringValueItemSourceJsonConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Volo.Abp.Validation.StringValues; + +namespace Volo.Abp.FeatureManagement.JsonConverters +{ + public class SelectionStringValueItemSourceJsonConverter : JsonConverter + { + public override ISelectionStringValueItemSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var newOptions = JsonSerializerOptionsHelper.Create(options, this); + + var rootElement = JsonDocument.ParseValue(ref reader).RootElement; + var items = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals("Items", StringComparison.InvariantCultureIgnoreCase)).Value.GetRawText(); + + var selectionStringValueItem = + JsonSerializer.Deserialize>(items, newOptions) ?? + new List(); + + return new StaticSelectionStringValueItemSource(selectionStringValueItem.ToArray()); + } + + public override void Write(Utf8JsonWriter writer, ISelectionStringValueItemSource value, JsonSerializerOptions options) + { + var newOptions = JsonSerializerOptionsHelper.Create(options, this); + JsonSerializer.Serialize(writer, value, value.GetType(), newOptions); + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs new file mode 100644 index 0000000000..32748231da --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/StringValueTypeJsonConverter.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Volo.Abp.Validation.StringValues; + +namespace Volo.Abp.FeatureManagement.JsonConverters +{ + public class StringValueTypeJsonConverter : JsonConverter + { + public override IStringValueType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var rootElement = JsonDocument.ParseValue(ref reader).RootElement; + var name = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals("Name", StringComparison.InvariantCultureIgnoreCase)).Value.GetString(); + var newOptions = JsonSerializerOptionsHelper.Create(options, this, new ValueValidatorJsonConverter(), new SelectionStringValueItemSourceJsonConverter()); + return name switch + { + "SelectionStringValueType" => JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions), + "FreeTextStringValueType" => JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions), + "ToggleStringValueType" => JsonSerializer.Deserialize(rootElement.GetRawText(), newOptions), + _ => throw new ArgumentException($"{nameof(IStringValueType)} named {name} was not found!") + }; + } + + public override void Write(Utf8JsonWriter writer, IStringValueType value, JsonSerializerOptions options) + { + var newOptions = JsonSerializerOptionsHelper.Create(options, this); + JsonSerializer.Serialize(writer, value, value.GetType(), newOptions); + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs new file mode 100644 index 0000000000..7276a36923 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/JsonConverters/ValueValidatorJsonConverter.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Volo.Abp.Json.SystemTextJson.JsonConverters; +using Volo.Abp.Validation.StringValues; + +namespace Volo.Abp.FeatureManagement.JsonConverters +{ + public class ValueValidatorJsonConverter : JsonConverter + { + public override IValueValidator Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var rootElement = JsonDocument.ParseValue(ref reader).RootElement; + var name = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals("Name", StringComparison.InvariantCultureIgnoreCase)).Value.GetString(); + var valueValidator = CreateValueValidatorByName(name); + + var propertiesRawText = rootElement.EnumerateObject().FirstOrDefault(x => x.Name.Equals("Properties", StringComparison.InvariantCultureIgnoreCase)).Value.GetRawText(); + var newOptions = JsonSerializerOptionsHelper.Create(options, this, new ObjectToInferredTypesConverter()); + var properties = JsonSerializer.Deserialize>(propertiesRawText, newOptions); + if (properties != null && properties.Any()) + { + foreach (var property in properties) + { + valueValidator.Properties[property.Key] = property.Value; + } + } + + return valueValidator; + } + + public override void Write(Utf8JsonWriter writer, IValueValidator value, JsonSerializerOptions options) + { + var newOptions = JsonSerializerOptionsHelper.Create(options, this); + JsonSerializer.Serialize(writer, value, value.GetType(), newOptions); + } + + protected virtual IValueValidator CreateValueValidatorByName(string name) + { + return name switch + { + "NULL" => new AlwaysValidValueValidator(), + "BOOLEAN" => new BooleanValueValidator(), + "NUMERIC" => new NumericValueValidator(), + "STRING" => new StringValueValidator(), + _ => throw new ArgumentException($"{nameof(IValueValidator)} named {name} was not found!") + }; + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/StringValueTypeJsonConverter.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/StringValueTypeJsonConverter.cs deleted file mode 100644 index 0ee276cce1..0000000000 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/StringValueTypeJsonConverter.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Validation.StringValues; - -namespace Volo.Abp.FeatureManagement -{ - public class StringValueTypeJsonConverter : JsonConverter, ITransientDependency - { - public override bool CanWrite => false; - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(IStringValueType); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException("This method should not be called to write (since CanWrite is false)."); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - if (reader.TokenType != JsonToken.StartObject) - { - return null; - } - - var jsonObject = JObject.Load(reader); - - var stringValue = CreateStringValueTypeByName(jsonObject, jsonObject["name"].ToString()); - foreach (var o in serializer.Deserialize>( - new JsonTextReader(new StringReader(jsonObject["properties"].ToString())))) - { - stringValue[o.Key] = o.Value; - } - - stringValue.Validator = CreateValueValidatorByName(jsonObject["validator"], jsonObject["validator"]["name"].ToString()); - foreach (var o in serializer.Deserialize>( - new JsonTextReader(new StringReader(jsonObject["validator"]["properties"].ToString())))) - { - stringValue.Validator[o.Key] = o.Value; - } - - return stringValue; - } - - protected virtual IStringValueType CreateStringValueTypeByName(JObject jObject, string name) - { - if (name == "SelectionStringValueType") - { - var selectionStringValueType = new SelectionStringValueType(); - if (jObject["itemSource"].HasValues) - { - selectionStringValueType.ItemSource = new StaticSelectionStringValueItemSource(jObject["itemSource"]["items"] - .Select(item => new LocalizableSelectionStringValueItem() - { - Value = item["value"].ToString(), - DisplayText = new LocalizableStringInfo(item["displayText"]["resourceName"].ToString(), item["displayText"]["name"].ToString()) - }).ToArray()); - } - - return selectionStringValueType; - } - - return name switch - { - "FreeTextStringValueType" => new FreeTextStringValueType(), - "ToggleStringValueType" => new ToggleStringValueType(), - _ => throw new ArgumentException($"{nameof(IStringValueType)} named {name} was not found!") - }; - } - - protected virtual IValueValidator CreateValueValidatorByName(JToken jObject, string name) - { - return name switch - { - "NULL" => new AlwaysValidValueValidator(), - "BOOLEAN" => new BooleanValueValidator(), - "NUMERIC" => new NumericValueValidator(), - "STRING" => new StringValueValidator(), - _ => throw new ArgumentException($"{nameof(IValueValidator)} named {name} was not found!") - }; - } - } -} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs index 5146061c3b..f062317d66 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/AbpFeatureManagementHttpApiModule.cs @@ -1,9 +1,12 @@ -using Localization.Resources.AbpUi; +using System.Collections.Generic; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.FeatureManagement.Localization; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.FeatureManagement.JsonConverters; namespace Volo.Abp.FeatureManagement { @@ -28,6 +31,11 @@ namespace Volo.Abp.FeatureManagement .Get() .AddBaseTypes(typeof(AbpUiResource)); }); + + Configure(options => + { + options.JsonSerializerOptions.Converters.AddIfNotContains(new StringValueTypeJsonConverter()); + }); } } } diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs index 1b7553b773..c72cbddffd 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json; using Shouldly; using Volo.Abp.Json; using Volo.Abp.Validation.StringValues; @@ -67,8 +67,10 @@ namespace Volo.Abp.FeatureManagement }; var serialized = _jsonSerializer.Serialize(featureListDto, indented: true); + var featureListDto2 = _jsonSerializer.Deserialize(serialized); + featureListDto2.ShouldNotBeNull(); featureListDto2.Groups[0].Features[0].ValueType.ShouldBeOfType(); featureListDto2.Groups[0].Features[0].ValueType.Validator.ShouldBeOfType(); diff --git a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.Web.Tests/MyProjectNameWebTestBase.cs b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.Web.Tests/MyProjectNameWebTestBase.cs index ec29900d07..7c3f6330c8 100644 --- a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.Web.Tests/MyProjectNameWebTestBase.cs +++ b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.Web.Tests/MyProjectNameWebTestBase.cs @@ -1,10 +1,8 @@ using System.Net; using System.Net.Http; +using System.Text.Json; using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using Shouldly; using Volo.Abp.AspNetCore.TestBase; @@ -22,10 +20,7 @@ namespace MyCompanyName.MyProjectName protected virtual async Task GetResponseAsObjectAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) { var strResponse = await GetResponseAsStringAsync(url, expectedStatusCode); - return JsonConvert.DeserializeObject(strResponse, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); + return JsonSerializer.Deserialize(strResponse, new JsonSerializerOptions(JsonSerializerDefaults.Web)); } protected virtual async Task GetResponseAsStringAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK)