ASP.NET Core中如何通过ModelMetaDataIdentity获取ModelMetaData?
多态递归模型绑定器的嵌套绑定问题
类结构
abstract class Parent { } class X : Parent { Parent ParProp; } class Y : Parent { List<Parent> ListParProp; } class Z : Parent { //Other properties. }
绑定器现状
我为Parent类实现了自定义多态递归模型绑定器及对应提供程序。当前逻辑是:通过隐藏输入标识元素类型,用MetadataProvider.GetMetadataForType按类型绑定元素,再特殊绑定Parent类型的子元素(或嵌套IEnumerable中最终类型为Parent的元素),最后通过反射将绑定的子元素附加到父元素。
近期发现绑定器会过多循环调用Parent模型绑定器,原因是绑定子元素时未关联容器实例,绑定完成后无法自动将实例设置到容器中。
测试发现属性的绑定方式由ModelMetaData决定:
ModelMetaDataProvider接口的GetMetadataForProperty方法因属性类型为Parent会触发无限递归;ModelMetaDataIdentity的静态方法ForProperty(PropertyInfo propertyInfo, Type modelType, Type containerType)能创建包含正确ModelType和容器信息的标识,理论上可识别Parent子类实现作为容器类成员属性的情况,但ModelMetaData是抽象类,其唯一构造函数ModelMetaData(ModelMetaDataIdentity identity)为受保护,无法直接调用获取实例。
问题影响
系统虽能运行,但必须将大量变量设为可空类型——因为内置复杂对象绑定后,Parent类型属性为空,自动验证器会报错。
现有方案考量
- 反射调用受保护构造函数:极易因框架更新失效,不愿采用;
- 保留原有粗糙实现:将空值视为软件局限性,非理想方案。
希望找到合规方法,获取Parent子类实现的ModelMetaData并保留其属性关联信息,让自动绑定器承担更多工作。
补充代码(基于微软多态绑定器改造,处理嵌套多态绑定)
BinderProvider
public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context.Metadata.ModelType != typeof(ParentModel)) { return null; } var subclasses = new[] {typeof(X), typeof(Y), typeof(Z) }; //实际代码中包含所有Parent的实现类 var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>(); foreach (var type in subclasses) { var modelMetadata = context.MetadataProvider.GetMetadataForType(type); binders[type] = (modelMetadata, context.CreateBinder(modelMetadata)); } { Type type = typeof(Parent); var modelMetadata = context.MetadataProvider.GetMetadataForType(type); binders[type] = (modelMetadata, null); } return new ParentModelBinder(binders); }
Binder
public async Task BindModelAsync(ModelBindingContext bindingContext) { var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Parent)); var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue; IModelBinder modelBinder; ModelMetadata modelMetadata; Type? modelType = Type.GetType(modelsNamespace + modelTypeValue); if (modelType != null && binders.ContainsKey(modelType)) { (modelMetadata, modelBinder) = binders[modelType]; } else { bindingContext.Result = ModelBindingResult.Failed(); return; } var newBindingContext = DefaultModelBindingContext.CreateBindingContext( bindingContext.ActionContext, bindingContext.ValueProvider, modelMetadata, bindingInfo: null, bindingContext.ModelName); await modelBinder.BindModelAsync(newBindingContext); if (newBindingContext.Result.IsModelSet) { // 递归分配属性 var qemProperties = ReflectorHelper.GetParentMembersFromType(modelType); foreach (var property in qemProperties) { await BindProperty(property, newBindingContext); } // 设置ValidationState确保派生类型属性正确 bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry { Metadata = modelMetadata, }; } bindingContext.Result = newBindingContext.Result; }
当前绑定器的问题行为
- 提供程序获取元数据/绑定器组合列表,用于获取对应元数据和绑定器;
- 按类型绑定
Parent类型的模型; - 当
Parent实现类包含Parent类型属性(或最终类型为Parent的IEnumerable)时,会循环绑定这些属性并附加到父类型,原因是绑定Parent实现类时未包含容器信息。
现寻求解决方案,如何实现需求?
内容的提问来源于stack exchange,提问作者Nur Luski Levi
相关产品推荐
相关产品推荐

