[英]JSON deserialization of polymorphic and complex objects in ASP.Net Core without Newtonsoft
ASP.Net 中多態和復雜對象的反序列化是一個眾所周知的話題。 我遇到的常見解決方案依賴於JsonConverter或JsonSubTypes 。
但是,這里的挑戰是根本不使用Newtonsoft.Json ,而是依賴新的System.Text.Json和Microsoft.AspNetCore.Mvc.ModelBinding 。 原因:我的類已經被大量“Netwtonsoft 裝飾”,但是這種裝飾(類/屬性屬性)是針對 ASP.Net 反序列化以外的目的進行優化和定制的。
Microsoft 有一個依賴於此處描述的ModelBinder屬性的解決方案。 我能夠正確反序列化多態對象,但不能正確反序列化復雜對象。 也就是說,包含其他非多態對象集合的多態對象不會正確反序列化。
public abstract class Vehicle
{
public abstract string Kind { get; set; }
public string Make { get; set; }
public RepairRecord[]? RepairHistory { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
public class Car : Vehicle
{
public override string Kind { get; set; } = nameof(Car);
public int CylinderCount { get; set; }
}
public class Bicycle : Vehicle
{
public override string Kind { get; set; } = nameof(Bicycle);
public bool HasStand { get; set; }
}
public class RepairRecord
{
public DateTime DateTime { get; set; }
public string Description { get; set; }
}
[HttpPut]
public IActionResult Create([ModelBinder(typeof(VehicleModelBinder))] Vehicle vehicle)
{
_logger.LogInformation(vehicle.ToString());
return new OkResult();
}
問題:反序列化的車輛在Create()
方法中缺少RepairHistory
記錄。
我錯過了什么? 請指教。
完整的工作代碼如下。
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new VehicleModelBinderProvider());
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.UseAllOfForInheritance(); // enabling inheritance - this allows to maintain the inheritance hierarchy in any generated client model
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
public abstract class Vehicle
{
public abstract string Kind { get; set; }
public string Make { get; set; }
public RepairRecord[]? RepairHistory { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
public class Car : Vehicle
{
public override string Kind { get; set; } = nameof(Car);
public int CylinderCount { get; set; }
}
public class Bicycle : Vehicle
{
public override string Kind { get; set; } = nameof(Bicycle);
public bool HasStand { get; set; }
}
public class RepairRecord
{
public DateTime DateTime { get; set; }
public string Description { get; set; }
}
[ApiController]
[Route("")]
public class Controller : ControllerBase
{
private readonly ILogger<Controller> _logger;
public Controller(ILogger<Controller> logger)
{
_logger = logger;
}
[HttpPost]
public IActionResult Create([ModelBinder(typeof(VehicleModelBinder))] Vehicle vehicle)
{
_logger.LogInformation(vehicle.ToString());
return new OkResult();
}
}
public class VehicleModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Vehicle))
{
return null;
}
var subclasses = new[] { typeof(Car), typeof(Bicycle), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new VehicleModelBinder(binders);
}
}
public class VehicleModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public VehicleModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Vehicle.Kind));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue == nameof(Car))
{
(modelMetadata, modelBinder) = binders[typeof(Car)];
}
else if (modelTypeValue == nameof(Bicycle))
{
(modelMetadata, modelBinder) = binders[typeof(Bicycle)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
不幸的是,目前(2022 年 9 月)沒有好的解決方案可用。
在此處查看我與 Microsoft 的討論。 據說,當.Net 7可用時,通過[JsonDerivedType]
屬性解決問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.