簡體   English   中英

JSON 在沒有 Newtonsoft 的 ASP.Net Core 中反序列化多態和復雜對象

[英]JSON deserialization of polymorphic and complex objects in ASP.Net Core without Newtonsoft

ASP.Net 中多態和復雜對象的反序列化是一個眾所周知的話題。 我遇到的常見解決方案依賴於JsonConverterJsonSubTypes

但是,這里的挑戰是根本不使用Newtonsoft.Json ,而是依賴新的System.Text.JsonMicrosoft.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM