将对象绑定到 Web API 端点时指定自定义属性名称

Posted

技术标签:

【中文标题】将对象绑定到 Web API 端点时指定自定义属性名称【英文标题】:Specifying custom property name when binding object to Web API endpoint 【发布时间】:2017-08-08 09:18:24 【问题描述】:

我有一个 .Net Core Web API。当模型属性与请求正文匹配时,它会自动映射模型。例如,如果你有这个类:

public class Package

    public string Carrier  get; set; 
    public string TrackingNumber  get; set; 

如果请求正文是以下 JSON,它将正确地将其绑定到 POST 端点:


    carrier: "fedex",
    trackingNumber: "123123123"

我需要做的是指定要映射的自定义属性。例如,使用上面相同的类,如果 TrackingNumber 以 tracking_number 的形式出现,我需要能够映射到 JSON。

我该怎么做?

【问题讨论】:

【参考方案1】:

TejSoft's answer 默认情况下在 ASP.NET Core 3.0 Web APIs 中不起作用。

从 3.0 开始,从 ASP.NET Core 共享框架中删除了 ASP.NET Core Json.NET (Newtonsoft.Json) 子组件。宣布“Json.NET 将继续与 ASP.NET Core 一起工作,但它不会在盒子里与共享框架一起使用。”新添加的Json Api 声称专门针对高性能场景。

使用JsonPropertyName 属性设置自定义属性名称:

using System.Text.Json.Serialization;

public class Package

    [JsonPropertyName("carrier")]
    public string Carrier  get; set; 

    [JsonPropertyName("tracking_number")]
    public string TrackingNumber  get; set; 

【讨论】:

这是 .net core 3.0 的正确答案。 JsonProperty 对我们不起作用,很难弄清楚原因。进行此更改清除了一切。 我花了好几个小时才弄清楚... o.o【参考方案2】:

更改您的包类并为您希望映射到不同 json 字段的每个字段添加 JsonProperty 装饰。

public class Package

    [JsonProperty(PropertyName = "carrier")]
    public string Carrier  get; set; 

    [JsonProperty(PropertyName = "trackingNumber")]
    public string TrackingNumber  get; set; 

【讨论】:

[JsonProperty(PropertyName = "tracking_Number")] 如果我想使用 XML 序列化会发生什么?【参考方案3】:

我认为这也应该有效:

using Microsoft.AspNetCore.Mvc;
public class Package

     [BindProperty(Name ="carrier")]
     public string Carrier  get; set; 

     [BindProperty(Name ="trackingNumber")]
     public string TrackingNumber  get; set; 

【讨论】:

在 ASP.NET Core 中,这不适用于模型,但当您想要将“全局”属性(存在于多个/所有请求中)绑定到控制器本身。见Use BindProperty Attribute for Model Binding to Properties。【参考方案4】:

通过使用自定义转换器,您将能够实现您所需要的。 以下基于属性的组件套件可能适合您的需求,并且非常通用,以防您想扩展它。

基础属性类

定义IsMatch,它允许您定义对象属性是否与 json 属性匹配。

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public abstract class JsonDeserializationPropertyMatchAttribute : Attribute

    protected JsonDeserializationPropertyMatchAttribute()  

    public abstract bool IsMatch(JProperty jsonProperty);

示例实现:多反序列化名称

定义一个属性,允许您将多个名称关联到一个属性。 IsMatch 实现简单地遍历它们并尝试找到匹配项。

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class JsonDeserializationNameAttribute : JsonDeserializationPropertyNameMatchAttribute

    public string[] PropertyNames  get; private set; 

    public JsonDeserializationNameAttribute(params string[] propertyNames)
    
        this.PropertyNames = propertyNames;
    

    public override bool IsMatch(JProperty jsonProperty)
    
        return PropertyNames.Any(x => String.Equals(x, jsonProperty.Name, StringComparison.InvariantCultureIgnoreCase));
    

转换器为了将这两个属性绑定到 json 反序列化,需要以下转换器:

public class JsonDeserializationPropertyMatchConverter : JsonConverter

    public override bool CanConvert(Type objectType)
    
        return objectType.IsClass;
    

    public override bool CanWrite
    
        get
        
            return false;
        
    

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        var constructor = objectType.GetConstructor(new Type[0]);
        if (constructor == null)
            throw new JsonSerializationException("A parameterless constructor is expected.");

        var value = constructor.Invoke(null);

        var jsonObject = JObject.Load(reader);
        var jsonObjectProperties = jsonObject.Properties();

        PropertyInfo[] typeProperties = objectType.GetProperties();
        var typePropertyTuples = new List<Tuple<PropertyInfo, Func<JProperty, bool>>>();
        foreach (var property in typeProperties.Where(x => x.CanWrite))
        
            var attribute = property.GetCustomAttribute<JsonDeserializationPropertyMatchAttribute>(true);
            if (attribute != null)
                typePropertyTuples.Add(new Tuple<PropertyInfo, Func<JProperty, bool>>(property, attribute.IsMatch));
            else
                typePropertyTuples.Add(new Tuple<PropertyInfo, Func<JProperty, bool>>(property, (x) => false));
        

        foreach (JProperty jsonProperty in jsonObject.Properties())
        
            var propertyTuple = typePropertyTuples.FirstOrDefault(x => String.Equals(jsonProperty.Name, x.Item1.Name, StringComparison.InvariantCultureIgnoreCase) || x.Item2(jsonProperty));
            if (propertyTuple != null)
                propertyTuple.Item1.SetValue(value, jsonProperty.Value.ToObject(propertyTuple.Item1.PropertyType, serializer));
        

        return value;
    

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        throw new NotImplementedException();
    

示例使用上面粘贴的代码,并通过如下装饰类,我设法让对象正确反序列化:

[JsonConverter(typeof(JsonDeserializationPropertyMatchConverter))]
public class Package

    public string Carrier  get; set; 

    [JsonDeserializationName("Tracking_Number","anotherName")]
    public string TrackingNumber  get; set; 

输出 1

var input = " carrier: \"fedex\", trackingNumber: \"123123123\" ";
var output = JsonConvert.DeserializeObject<Package>(input); // output.TrackingNumber is "123123123"

输出 2

var input = " carrier: \"fedex\", tracking_Number: \"123123123\" ";
var output = JsonConvert.DeserializeObject<Package>(input); // output.TrackingNumber is "123123123"

输出 3

var input = " carrier: \"fedex\", anotherName: \"123123123\" ";
var output = JsonConvert.DeserializeObject<Package>(input); // output.TrackingNumber is "123123123"

【讨论】:

【参考方案5】:

由于某种原因,以下在我的模型类中对我不起作用:

[JsonPropertyName("carrier")]
 public string Carrier  get; set; 

虽然这有效:

[JsonProperty(PropertyName = "carrier")]
public string Carrier  get; set; 

一个来自 system.text.Json 库,另一个来自 Newtonsoft.json,但我真的很想知道为什么会这样。由于我试图在 API 调用中返回 Json 字符串中的属性值。

【讨论】:

我也遇到过这个问题。你找到原因了吗?在我的例子中,“[JsonPropertyName("carrier")]" 适用于一些属性,而对于其余属性,我必须在单个模型中使用 "[JsonProperty(PropertyName = "carrier")]"。【参考方案6】:

如有要求:

[BindProperty(Name = "tracking_number")]
public string TrackingNumber  get; set; 

若有回应:

[JsonPropertyName("tracking_number")]
public string TrackingNumber  get; set; 

在这两种情况下:

[BindProperty(Name = "tracking_number")]
[JsonPropertyName("tracking_number")]
public string TrackingNumber  get; set; 

【讨论】:

【参考方案7】:

在我的情况下,我不想更改属性名称 CarrierTrackingNumber

所以我只是在 JsonResult 响应中添加 new JsonSerializerSettings()

public JsonResult GetJQXGridData()
    var Data = .......
    return Json(Data, new JsonSerializerSettings()) //change here

不使用JsonSerializerSettings输出


    carrier: "fedex",
    trackingNumber: "123123123"

使用JsonSerializerSettings 输出


    Carrier: "fedex",
    TrackingNumber: "123123123"

【讨论】:

【参考方案8】:

对于DotnetCore3.1我们可以使用

public class Package


    [JsonProperty("carrier")]
    public string Carrier  get; set; 

    [JsonProperty("trackingNumber")]
    public string TrackingNumber  get; set; 

【讨论】:

这个对我有用。【参考方案9】:

如果你使用 XML 序列化 (ContentTypes.TextXml) 你可以使用属性 [XmlElement(ElementName = "name")] 更改字段名称。使用命名空间 System.Xml.Serialization。

我尝试了 [JsonProperty("name")][System.Runtime.Serialization.DataMember(Name = "name")],但它们不适用于我的 XML 内容。

更多信息请阅读https://docs.microsoft.com/en-us/dotnet/standard/serialization/controlling-xml-serialization-using-attributes

【讨论】:

以上是关于将对象绑定到 Web API 端点时指定自定义属性名称的主要内容,如果未能解决你的问题,请参考以下文章

ASP Web API:将对象序列化为 JSON 时指定自定义字段名称

复杂抽象对象的WebAPI自定义模型绑定

调用 Web API 2 端点时出现 HTTP 415 不支持的媒体类型错误

Ninja 框架端点在尝试将 JSON 映射到自定义对象时抛出 500 错误

反序列化 Web.API 参数时未调用自定义 Json.NET JsonConverter

在.net core web api中添加自定义属性到OpenAPI规范文件和swagger