Abp框架版本: 5.0.0-beta2 2,UI: Blazor WebAssembly
我试图在ABP框架中实现多态,以便能够在API后端和Blazor前端之间交换派生类,并且很难从多个方面反序列化JSON:
// Output Dtos
public abstract class AnimalOutputDto : EntityDto<Guid>
{
public string Name { get; set; }
}
public class CatOutputDto : AnimalOutputDto
{
public string Name { get; set; }
public string Color { get; set; }
}
// Input Dtos
public abstract class AnimalInputDto : EntityDto<Guid>
{
public string Name { get; set; }
}
public class CatInputDto : AnimalInputDto
{
public string Name { get; set; }
public string Color { get; set; }
}
当将模型从Blazor前端传递到HTTPAPI时,我可以使用本文中描述的自定义JsonConverter正确地反序列化它们,我将其添加到HTTPAPI项目中,然后在HTTPAPI.Host项目的ConfigureServices方法中引用:
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
..Usual configuration statements..
ConfigureJsonConverters(context);
}
private void ConfigureJsonConverters(ServiceConfigurationContext context)
{
context.Services.AddControllers(options =>
{
}).AddJsonOptions(options => {
options.JsonSerializerOptions.Converters.Add(new AnimalJsonConverter());
});
}
当模型被传递回Blazor前端时,我可以使用Microsoft文章中也指定的类型的正确转换器来验证它是否被序列化。
但是,当Blazor接收到模型时,会抛出一个异常:它显然没有识别多态类型,而是试图反序列化抽象基类:
Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported.
似乎我需要找到一种方法,在Blazor项目中注册相同的自定义JSON转换器类,就像在HttpApi.Host项目中那样。但是,我找不到任何关于如何做到这一点的文档。
有人对此有任何信息或指导吗?
发布于 2021-11-21 10:29:28
通过使用JsonConvert类和JsonConverter属性,我成功地做到了这一点。这种方式在ConfigureServices()方法中没有必要的配置。
。
。
行中创建了一个适当的转换器类(在与DTO相同的项目中)
DTO课程:
[JsonConvert(typeof(AnimalInputJsonConverter))]
public abstract class AnimalInputDto : EntityDto<Guid>
{
public string Name { get; set; }
public virtual AnimalType AnimalType => AnimalType.NotSelected
}
public class CatInputDto : AnimalInputDto
{
public override AnimalType AnimalType => AnimalType.Cat
[.. more properties specific to Cat]
}
[JsonConvert(typeof(AnimalOutputJsonConverter))]
public abstract class AnimalOutputDto : EntityDto<Guid>
{
public string Name { get; set; }
public virtual AnimalType AnimalType => AnimalType.NotSelected
}
public class CatOutputDto : AnimalOutputDto
{
public override AnimalType AnimalType => AnimalType.Cat
[.. more properties specific to Cat]
}
转换器示例(输入和输出DTO之间的代码本质上是相同的)
public class AnimalInputDtoJsonConverter : JsonConverter<AnimalInputDto>
{
public override bool CanConvert(Type typeToConvert) =>
typeof(AnimalInputDto).IsAssignableFrom(typeToConvert);
public override AnimalInputDto Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Take a copy of the reader as we need to check through the object first before deserializing.
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
AnimalType typeDiscriminator = AnimalType.NotSelected;
string camelCasedPropertyName =
nameof(AnimalDto.AnimalType).ToCamelCase();
// Loop through the JSON tokens. Look for the required property by name.
while (readerClone.Read())
{
if (readerClone.TokenType == JsonTokenType.PropertyName && readerClone.GetString() == camelCasedPropertyName)
{
// Move on to the value, which has to parse out to an enum
readerClone.Read();
if (readerClone.TokenType == JsonTokenType.Number)
{
int value = readerClone.GetInt32();
try
{
typeDiscriminator = (AnimalType)value;
break;
}
catch (InvalidCastException)
{
throw new JsonException($"{value} is not a recognised integer representation of {typeof(AnimalType)}");
}
}
}
}
AnimalInputDto target = typeDiscriminator switch
{
AnimalType.Cat => JsonSerializer.Deserialize<CatInputDto>(ref reader, options),
_ => throw new NotSupportedException($"The supplied object is not a recognised derivative of {typeof(AnimalInputDto)}")
};
return target;
}
public override void Write(
Utf8JsonWriter writer,
AnimalInputDto value,
JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
此外,一种通用方法似乎是可能的,尽管这段代码没有被优化或性能测试,但我期望使用反射和实例化的对象使用Activator.CreateInstance()来检查其鉴别器的值会对性能造成影响。
请注意,下面假设区分器属性是枚举属性,并且派生类的此属性名与枚举类型完全相同:
用途如下:
[JsonConvert(typeof(PolymorphicJsonConverter<AnimalInputDto, AnimalType>))]
public abstract class AnimalInputDto : EntityDto<Guid>
{
public string Name { get; set; }
public virtual AnimalType AnimalType => AnimalType.NotSelected
}
...
public class PolymorphicJsonConverter<T, U> : JsonConverter<T>
where T : EntityDto<Guid>
where U : Enum
{
public string TypeDiscriminator { get; private set; }
public string TypeDiscriminatorCamelCase { get; private set; }
public List<Type> DerivableTypes { get; private set; }
public PolymorphicJsonConverter()
: base()
{
TypeDiscriminator = typeof(U).Name;
TypeDiscriminatorCamelCase = TypeDiscriminator.ToCamelCase();
DerivableTypes = new List<Type>();
foreach (var domainAssembly in AppDomain.CurrentDomain.GetAssemblies())
{
var assemblyTypes = domainAssembly.GetTypes()
.Where(type => type.IsSubclassOf(typeof(T)) && !type.IsAbstract);
DerivableTypes.AddRange(assemblyTypes);
}
}
public override bool CanConvert(Type typeToConvert) =>
typeof(T).IsAssignableFrom(typeToConvert);
public override T Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Take a copy of the reader as we need to check through the object first before deserializing.
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
U typeDiscriminatorValue = (U)Enum.ToObject(typeof(U), 0);
// Loop through the JSON tokens. Look for the required property by name.
while (readerClone.Read())
{
if (readerClone.TokenType == JsonTokenType.PropertyName && readerClone.GetString() == TypeDiscriminatorCamelCase)
{
// Move on to the value, which has to parse out to an enum
readerClone.Read();
if (readerClone.TokenType == JsonTokenType.Number)
{
int value = readerClone.GetInt32();
try
{
typeDiscriminatorValue = (U)Enum.ToObject(typeof(U), value);
break;
}
catch (InvalidCastException)
{
throw new NotSupportedException($"{value} is not a recognised integer representation of {typeof(U)}");
}
}
}
}
T target = null;
foreach(var dt in DerivableTypes)
{
var newInst = Activator.CreateInstance(dt);
var propValue = (U)newInst.GetType().GetProperty(TypeDiscriminator).GetValue(newInst, null);
if (propValue.Equals(typeDiscriminatorValue))
{
target = (T)JsonSerializer.Deserialize(ref reader, dt, options);
}
}
if (target == null)
{
throw new NotSupportedException($"The supplied object is not a recognised derivative of {typeof(T)}");
}
return target;
}
public override void Write(
Utf8JsonWriter writer,
T value,
JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
以上/进一步阅读的启示:https://getyourbitstogether.com/polymorphic-serialization-in-system-text-json/ https://vpaulino.wordpress.com/2021/02/23/deserializing-polymorphic-types-with-system-text-json/ https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-6-0 https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonconverter-1?view=net-6.0 https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonconverterattribute?view=net-6.0
发布于 2021-11-19 10:16:29
使用System.Text.Json
仍然有一些限制-请看这里:https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#table-of-differences-between-newtonsoftjson-and-systemtextjson
尽管它有一个解决办法,多态序列化和反序列化似乎就是其中之一。
我认为您只能在Blazor侧使用Newtonsoft.Json
。
总是使用Newtonsoft.Json
如果要继续对所有类型使用Newtonsoft.Json库,可以在模块类的PreConfigureServices
方法中将UseHybridSerializer
设置为false:
PreConfigure(options => { options.UseHybridSerializer = false;});
参考资料:
https://stackoverflow.com/questions/70032776
复制相似问题