根据特定条件反序列化请求体中的接口asp.net核心

Deserialize interface in request body based on specific condition asp.net core

我正在使用 ASP.Net Core 2.2.

制作一个小型 Web 应用程序

目前我有一个接口:

public interface ILocation {

    public int Type {get;}
}

有 2 个 class 实现了这个 ILocation 接口:


public class StreetLocation: ILocation {

    public int Type {get;set;}

    public string City {get;set;}

    public string State {get;set;}
}

public class GeoCoordinateLocation: ILocation {

    public int Type {get;set;}

    public double Latitude{get;set;}

    public double Longitude{get;set;}
}

我有一个class StudentViewModel 用于接收从客户端发送到服务器的信息。这个class实现如下:

public class StudentViewModel {

    public Guid Id {get;set;}

    public string Name {get;set;}

    public ILocation Address {get;set;}
}

我从客户端发送的请求是这样的:

{
    "id": "0da28089-c0da-41a7-a47f-89b54d52822b",
    "name": "Student 01",
    "location": {
        "type": 1,
        ...
    }
}

如果 location type 为 1,ILocation 将反序列化为 StreetLocation,如果 则反序列化为 GeoCoordinateLocation位置类型 是 2.

请问有什么解决办法吗? asp.net core 中的 IModelBinder 可以完成吗?

谢谢

您可以创建自定义 JsonConverter 来做到这一点。

  1. 创建一个辅助函数来在运行时解析位置类型:Type ResolveILocationTypeRuntime(typeCode)
  2. 创建一个辅助函数以在运行时创建某个特定类型的实例:DeserializeLocationRuntime(json, type)
  3. 根据当前 type.
  4. 读取 Json 或写入 Json

实施:

public class CustomLocationConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var json= JToken.ReadFrom(reader);
        var typeToken = json.SelectToken("type"); // or ??json.SelectToken("Type");
        if(typeToken ==null) return null;         //  
        var type= ResolveILocationTypeRuntime((int)typeToken);
        var location = DeserializeLocationRuntime(json, type);
        return location;
    }

    private Type ResolveILocationTypeRuntime(int type)
    {
        switch (type)
        {
            case 1:
                return typeof(StreetLocation);
            case 2:
                return typeof(GeoCoordinateLocation);
            default:
                throw new ArgumentOutOfRangeException("type should be 1|2");
        }
    }
    private ILocation DeserializeLocationRuntime(JToken json, Type locationType)
    {
        MethodInfo mi = typeof(JToken)
            .GetMethods(BindingFlags.Public | BindingFlags.Instance)
            .Where(m => m.Name == "ToObject" && m.GetParameters().Length == 0 && m.IsGenericMethod)
            .FirstOrDefault()
            ?.MakeGenericMethod(locationType);
        var location = mi?.Invoke(json, null);
        return (ILocation)location;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var location = value as ILocation;
        var type = ResolveILocationTypeRuntime(location.Type);
        serializer.Serialize(writer, location, type );
    }
}

并用 JsonConverterAttribute:

装饰你的 Address 属性
public class StudentViewModel
{
    public Guid Id { get; set; }
    public string Name { get; set; }

    [JsonProperty("location")]
    [JsonConverter(typeof(CustomLocationConverter))]

    public ILocation Address { get; set; }
}

测试用例:

控制器动作 :

public IActionResult Test([FromBody]StudentViewModel model)
{
    return Json(model);
}

要求:

POST https://localhost:5001/home/test HTTP/1.1
Content-Type: application/json

{
    "id": "0da28089-c0da-41a7-a47f-89b54d52822b",
    "name": "Student 01",
    "location": {
        "type":2,
        "latitude": 1.2,
        "longitude":3.1415926,
    }
}