.NET Core 多层嵌套继承对象的自定义模型绑定实现问题
是否可以为嵌套继承对象创建自定义Custom Model Binder?
我已查阅官方的自定义模型绑定文档,但我的场景更为复杂,涉及的对象结构复杂度很高。
以下是我使用的简化结构示例:
第一层
public class Top { public string Value { set; get; } public IEnumerable<MiddleBase> MiddleClasses { set; get; } }
第二层
public abstract class MiddleBase { public ConcreteMiddleType Type { set; get; } public IEnumerable<BottomBase> BottomClasses { set; get; } } public class ConcreteMiddle1 : MiddleBase { public string Value1 { set; get; } } public class ConcreteMiddle2 : MiddleBase { public string Value2 { set; get; } }
第三层
public abstract class BottomBase { public ConcreteBottomType Type { set; get; } } public class ConcreteBottom1 : BottomBase { public string Value1 { set; get; } } public class ConcreteBottom2 : BottomBase { public string Value2 { set; get; } }
我在处理请求时出现报错,无法完成抽象基类的绑定。请问是否有开发者遇到过同类问题,有相关处理经验可以分享?
解决方案
可以实现,你需要为每层抽象基类分别编写自定义模型绑定器,通过Type枚举字段判断要实例化的具体子类,再复用默认绑定逻辑完成子类属性的绑定,同时支持递归处理嵌套的抽象类集合。
步骤1:实现MiddleBase自定义绑定器
public class MiddleBaseModelBinder : IModelBinder { public async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); // 读取当前对象的Type枚举值 var typeValueResult = bindingContext.ValueProvider.GetValue($"{bindingContext.ModelName}.{nameof(MiddleBase.Type)}"); if (typeValueResult == ValueProviderResult.None || !Enum.TryParse<ConcreteMiddleType>(typeValueResult.FirstValue, out var middleType)) { bindingContext.Result = ModelBindingResult.Failed(); return; } // 根据Type实例化对应的具体子类 MiddleBase instance = middleType switch { ConcreteMiddleType.Middle1 => new ConcreteMiddle1(), ConcreteMiddleType.Middle2 => new ConcreteMiddle2(), _ => null }; if (instance == null) { bindingContext.Result = ModelBindingResult.Failed(); return; } // 绑定实例的所有属性,包括嵌套的BottomBase集合(会自动触发BottomBase的绑定器) bindingContext.Model = instance; var propertyBindingInfo = bindingContext.ValidationState[instance]; propertyBindingInfo.SuppressValidation = false; foreach (var property in bindingContext.ModelMetadata.Properties) { var propertyBindingContext = DefaultModelBindingContext.CreateBindingContext( bindingContext.ActionContext, bindingContext.ValueProvider, property, bindingInfo: null, modelName: $"{bindingContext.ModelName}.{property.PropertyName}"); await bindingContext.ModelBinderFactory.CreateBinder(propertyBindingContext).BindModelAsync(propertyBindingContext); if (propertyBindingContext.Result.IsModelSet) { property.PropertySetter.SetValue(bindingContext.Model, propertyBindingContext.Result.Model); } } bindingContext.Result = ModelBindingResult.Success(instance); } }
步骤2:实现BottomBase自定义绑定器
逻辑和MiddleBase绑定器完全一致,只需要替换对应的枚举和基类即可:
public class BottomBaseModelBinder : IModelBinder { public async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); var typeValueResult = bindingContext.ValueProvider.GetValue($"{bindingContext.ModelName}.{nameof(BottomBase.Type)}"); if (typeValueResult == ValueProviderResult.None || !Enum.TryParse<ConcreteBottomType>(typeValueResult.FirstValue, out var bottomType)) { bindingContext.Result = ModelBindingResult.Failed(); return; } BottomBase instance = bottomType switch { ConcreteBottomType.Bottom1 => new ConcreteBottom1(), ConcreteBottomType.Bottom2 => new ConcreteBottom2(), _ => null }; if (instance == null) { bindingContext.Result = ModelBindingResult.Failed(); return; } bindingContext.Model = instance; foreach (var property in bindingContext.ModelMetadata.Properties) { var propertyBindingContext = DefaultModelBindingContext.CreateBindingContext( bindingContext.ActionContext, bindingContext.ValueProvider, property, bindingInfo: null, modelName: $"{bindingContext.ModelName}.{property.PropertyName}"); await bindingContext.ModelBinderFactory.CreateBinder(propertyBindingContext).BindModelAsync(propertyBindingContext); if (propertyBindingContext.Result.IsModelSet) { property.PropertySetter.SetValue(bindingContext.Model, propertyBindingContext.Result.Model); } } bindingContext.Result = ModelBindingResult.Success(instance); } }
步骤3:实现绑定器提供者
避免手动给每个属性加[ModelBinder]特性,自动匹配抽象基类对应的绑定器:
public class CustomModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context.Metadata.ModelType == typeof(MiddleBase)) { return new BinderTypeModelBinder(typeof(MiddleBaseModelBinder)); } if (context.Metadata.ModelType == typeof(BottomBase)) { return new BinderTypeModelBinder(typeof(BottomBaseModelBinder)); } return null; } }
步骤4:注册绑定器
在Program.cs中添加配置:
builder.Services.AddControllers(options => { options.ModelBinderProviders.Insert(0, new CustomModelBinderProvider()); });
注意事项
- 如果你是用JSON格式提交请求,也可以直接用
System.Text.Json的JsonConverter实现多态序列化/反序列化,实现起来比模型绑定器更简洁,只需要为MiddleBase和BottomBase分别编写JsonConverter即可。 - 绑定逻辑中复用了默认的属性绑定逻辑,不需要手动解析每个字段,支持默认绑定的所有特性,包括数据注解验证。
内容的提问来源于stack exchange,提问作者Konstantin Shutko
相关产品推荐
相关产品推荐

