You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 2控制器中编写自定义JsonConverter处理POST多态JSON反序列化

实现安全的多态JSON反序列化自定义JsonConverter(ASP.NET MVC Core 2)

我之前在ASP.NET MVC Core 2项目里刚好解决过一模一样的问题——既要处理多态类型数组的反序列化,又得避开TypeNameHandling的安全风险,下面是我实战验证过的完整方案,完全适配默认模型绑定:

核心思路

我们不用自带的TypeNameHandling,而是手动在JSON数据里携带可控的类型标识,同时维护一个类型白名单,只允许我们预先定义的类型被反序列化,从根源上避免安全风险。

1. 定义类型约定与白名单

首先我们约定每个多态对象必须携带一个typeDiscriminator字段,值是我们预定义的类型别名,再用字典把别名映射到具体类型:

// 类型鉴别器的常量定义,统一管理字段名和允许的类型
public static class TypeDiscriminatorConstants
{
    public const string Key = "typeDiscriminator";
    // 白名单:只允许这里列出的类型被反序列化
    public static readonly Dictionary<string, Type> AllowedTypes = new Dictionary<string, Type>
    {
        { "Car", typeof(Car) },
        { "Truck", typeof(Truck) },
        { "Motorcycle", typeof(Motorcycle) }
    };
}

// 基类和派生类示例
public abstract class Vehicle
{
    public string Model { get; set; }
}

public class Car : Vehicle
{
    public int NumberOfDoors { get; set; }
}

public class Truck : Vehicle
{
    public int LoadCapacity { get; set; }
}

public class Motorcycle : Vehicle
{
    public bool HasSideCar { get; set; }
}

2. 编写自定义JsonConverter

这个转换器专门处理Vehicle[]类型的反序列化,核心逻辑是:读取每个对象的类型标识,从白名单找到对应类型,再反序列化:

public class PolymorphicVehicleConverter : JsonConverter<Vehicle[]>
{
    public override Vehicle[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartArray)
        {
            throw new JsonException("请求体必须是数组格式");
        }

        var vehicleList = new List<Vehicle>();

        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndArray) break;

            // 先把当前对象解析为JsonDocument,方便提取类型标识
            using var doc = JsonDocument.ParseValue(ref reader);
            var rootElement = doc.RootElement;

            // 检查是否包含必填的类型标识字段
            if (!rootElement.TryGetProperty(TypeDiscriminatorConstants.Key, out var typeAliasElement))
            {
                throw new JsonException($"缺少必填字段:{TypeDiscriminatorConstants.Key}");
            }

            var typeAlias = typeAliasElement.GetString();
            // 验证类型是否在白名单内
            if (!TypeDiscriminatorConstants.AllowedTypes.TryGetValue(typeAlias, out var targetType))
            {
                throw new JsonException($"不允许的类型:{typeAlias}");
            }

            // 反序列化为目标类型并加入列表
            var vehicle = (Vehicle)JsonSerializer.Deserialize(rootElement.GetRawText(), targetType, options);
            vehicleList.Add(vehicle);
        }

        return vehicleList.ToArray();
    }

    public override void Write(Utf8JsonWriter writer, Vehicle[] value, JsonSerializerOptions options)
    {
        // 如果需要序列化多态数组返回给前端,这里补充写入类型标识的逻辑
        writer.WriteStartArray();
        foreach (var vehicle in value)
        {
            writer.WriteStartObject();
            // 写入类型标识
            var typeAlias = TypeDiscriminatorConstants.AllowedTypes.First(kvp => kvp.Value == vehicle.GetType()).Key;
            writer.WriteString(TypeDiscriminatorConstants.Key, typeAlias);
            // 写入对象的其他属性
            using var doc = JsonDocument.Parse(JsonSerializer.Serialize(vehicle, options));
            foreach (var property in doc.RootElement.EnumerateObject())
            {
                if (property.Name != TypeDiscriminatorConstants.Key)
                {
                    property.WriteTo(writer);
                }
            }
            writer.WriteEndObject();
        }
        writer.WriteEndArray();
    }
}

3. 注册转换器到MVC Core 2的默认模型绑定

在Startup.cs的ConfigureServices方法里,把自定义转换器添加到JSON序列化配置中,这样模型绑定时就会自动使用它:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
        .AddJsonOptions(options =>
        {
            // 添加自定义多态转换器
            options.SerializerOptions.Converters.Add(new PolymorphicVehicleConverter());
            // 关闭默认的类型解析,确保安全
            options.SerializerOptions.TypeInfoResolver = null;
        });
}

4. 前端jQuery POST请求示例

确保前端发送的JSON数组中,每个对象都包含typeDiscriminator字段:

var vehiclesData = [
    {
        typeDiscriminator: "Car",
        model: "特斯拉Model 3",
        numberOfDoors: 4
    },
    {
        typeDiscriminator: "Truck",
        model: "福特F-150",
        loadCapacity: 1000
    }
];

$.ajax({
    url: "/Vehicle/AddVehicles",
    type: "POST",
    contentType: "application/json",
    data: JSON.stringify(vehiclesData),
    success: function(res) {
        console.log("提交成功!", res);
    },
    error: function(err) {
        console.error("提交失败:", err);
    }
});

5. 控制器Action示例

此时模型绑定会自动把请求体的JSON数组反序列化为正确的派生类型数组:

public class VehicleController : Controller
{
    [HttpPost]
    public IActionResult AddVehicles([FromBody] Vehicle[] vehicles)
    {
        foreach (var vehicle in vehicles)
        {
            switch (vehicle)
            {
                case Car car:
                    // 处理轿车类型,比如获取NumberOfDoors
                    Console.WriteLine($"收到轿车:{car.Model},车门数:{car.NumberOfDoors}");
                    break;
                case Truck truck:
                    // 处理卡车类型,比如获取LoadCapacity
                    Console.WriteLine($"收到卡车:{truck.Model},载重:{truck.LoadCapacity}kg");
                    break;
                case Motorcycle moto:
                    // 处理摩托车类型
                    Console.WriteLine($"收到摩托车:{moto.Model},带边车:{moto.HasSideCar}");
                    break;
            }
        }
        return Ok("处理完成");
    }
}

关键注意事项

  • 安全保障:通过AllowedTypes白名单,完全杜绝了TypeNameHandling可能带来的任意类型注入攻击,只有我们预先认可的类型才能被反序列化。
  • 扩展性:如果需要新增派生类型,只需要在AllowedTypes字典里添加对应的别名和类型即可,无需修改转换器核心代码。
  • 兼容性:这个方案适配ASP.NET MVC Core 2默认的System.Text.Json序列化器,如果你的项目用的是Newtonsoft.Json,转换器的写法会略有不同,但核心思路一致。

内容的提问来源于stack exchange,提问作者James Morrison

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 11:05:31