如何在.NET Core中绑定模型的多态属性?
实现方案
针对你遇到的[FromBody]场景下的属性多态绑定需求,优先选择JSON序列化器层面的多态配置方案,比自定义模型绑定器更简洁高效,以下是按优先级排序的实现方式:
方案1:System.Text.Json 原生多态配置(适用ASP.NET Core 7+)
.NET 7及以上版本的System.Text.Json原生支持多态序列化,只需给基类加特性配置即可:
// 为基类指定派生类和对应的类型鉴别器 [JsonDerivedType(typeof(AndExpression), typeDiscriminator: "And")] [JsonDerivedType(typeof(MatchesExpression), typeDiscriminator: "Matches")] public class Expression { // 标注为类型鉴别器字段 [JsonDiscriminator] public string Type {get;set;} }
请求传参时在query节点内增加type字段即可自动反序列化为对应子类,示例:
{ "pageSize": 10, "query": { "type": "Matches", "fieldId": "body", "value": "cake", "operator": "matches" } }
方案2:自定义JsonConverter(适用所有ASP.NET Core版本)
如果是.NET 6及更早版本,或者不想增加类型鉴别字段,可以自定义JsonConverter处理Expression类型的反序列化:
第一步:编写Converter实现
public class ExpressionJsonConverter : JsonConverter<Expression> { public override Expression Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using var jsonDoc = JsonDocument.ParseValue(ref reader); var root = jsonDoc.RootElement; // 方式1:根据type字段判断类型 if (root.TryGetProperty("type", out var typeProp)) { var typeValue = typeProp.GetString(); return typeValue switch { "Matches" => JsonSerializer.Deserialize<MatchesExpression>(root.GetRawText(), options), "And" => JsonSerializer.Deserialize<AndExpression>(root.GetRawText(), options), _ => throw new NotSupportedException($"不支持的表达式类型:{typeValue}") }; } // 方式2:根据字段特征自动判断,无需传type if (root.TryGetProperty("fieldId", out _) && root.TryGetProperty("operator", out _)) { return JsonSerializer.Deserialize<MatchesExpression>(root.GetRawText(), options); } if (root.TryGetProperty("expressions", out _)) { return JsonSerializer.Deserialize<AndExpression>(root.GetRawText(), options); } throw new InvalidOperationException("无法识别的表达式类型"); } public override void Write(Utf8JsonWriter writer, Expression value, JsonSerializerOptions options) { // 序列化时保留实际子类的字段 JsonSerializer.Serialize(writer, value, value.GetType(), options); } }
第二步:注册Converter
在Program.cs中配置全局Json选项:
builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new ExpressionJsonConverter()); });
该方案无需修改现有业务代码,也不用自定义模型绑定器,即可实现Query属性的多态反序列化。
方案3:自定义ModelBinder(仅特殊场景使用)
如果确实需要通过模型绑定器实现(比如非JSON传参场景),可以参考以下实现:
第一步:编写模型绑定器
public class SearchModelBinder : IModelBinder { public async Task BindModelAsync(ModelBindingContext bindingContext) { var request = bindingContext.HttpContext.Request; request.EnableBuffering(); using var reader = new StreamReader(request.Body, leaveOpen: true); var bodyContent = await reader.ReadToEndAsync(); request.Body.Position = 0; using var jsonDoc = JsonDocument.Parse(bodyContent); var root = jsonDoc.RootElement; var search = new Search(); // 绑定普通属性 if (root.TryGetProperty("pageSize", out var pageSizeProp)) { search.PageSize = pageSizeProp.GetInt32(); } // 绑定多态Query属性 if (root.TryGetProperty("query", out var queryProp)) { if (queryProp.TryGetProperty("fieldId", out _)) { search.Query = JsonSerializer.Deserialize<MatchesExpression>(queryProp.GetRawText()); } else if (queryProp.TryGetProperty("expressions", out _)) { search.Query = JsonSerializer.Deserialize<AndExpression>(queryProp.GetRawText()); } } bindingContext.Result = ModelBindingResult.Success(search); } }
第二步:绑定到Search类
[ModelBinder(BinderType = typeof(SearchModelBinder))] public class Search { public int PageSize {get;set;} public Expression Query{get;set;} }
注意事项
[FromBody]的绑定逻辑默认委托给JSON序列化器处理,方案1、2是最优解,无需侵入模型绑定流程- 优先使用类型鉴别字段的方式判断类型,比字段特征判断更稳定,避免后续业务迭代导致的判断逻辑异常
- 如果项目使用Newtonsoft.Json而非默认的System.Text.Json,可编写对应版本的自定义JsonConverter,逻辑完全一致
内容的提问来源于stack exchange,提问作者iasksillyquestions
相关产品推荐
相关产品推荐

