当传入数据与操作参数不同时如何正确实现 DefaultModelBinder
How to propertly Implement DefaultModelBinder when incoming data is different than action argument
我有一个自定义模型活页夹,可以将可以反序列化为 IEnumerable<GenericModel>
的请求转换为操作参数 IEnumerable<IDataItem>
。我无法弄清楚要从 DefaultModelBinder
覆盖哪个方法,如何实现它,以及它到底应该做什么 return。
最近几天通过谷歌进行梳理并找到了大量信息,我仍然没有很好地理解虚拟方法 BindModel vs. CreateModel vs. OnModelUpdated vs. 等的明确目的
请求是 json 个 GenericModel
数组,需要将其转换为 IEnumerable<IDataItem>
以执行此操作...
public ActionResult Save(IEnumerable<IDataItem> models)
我的模型活页夹(继承自 DefaultModelBinder
)正在实施 CreateModel
并尝试手动将 bindingContext.ModelType
从 IDataItem
设置为 GenericModel
以便我可以为每个 属性 正确获取 PropertyDescriptor
,获取其值,并为 GenericModel
实例设置属性。一旦我有一个水合的 GenericModel
我会创建正确的混凝土 IDataItem
和 return 它回到 DefaultModelBinder
继续它的绑定和验证(我需要因为一些属性IDataItem
装饰有 ValidationAttributes
).
问题是我永远无法弄清楚如何正确地遍历 GenericModel
属性并从 ValueProvider
设置它的值。
我的想法是我可以将 bindingContext.ModelType
从 IDataItem
更改为 GenericModel
并遍历它的 PropertyDescriptors
,但是 bindingContext.ModelType
没有a setter,虽然它似乎源自 bindingContext.ModelMetadata.Model
,但更改无效 - bindingContext.ModelType
仍然是 IDataItem
.
public class DataItemModelBinder : DefaultModelBinder {
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) {
GenericModel model = CreateGenericModel(controllerContext, bindingContext);
// just return a dummy instance of IDataItem so I can debug
return new TextboxModel();
}
private GenericModel CreateGenericModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
GenericModel model = new GenericModel();
// I was thinking this would change bindingContext.ModelType but it doesn't
bindingContext.ModelMetadata.Model = model;
foreach (PropertyDescriptor descriptor in GetFilteredModelProperties(controllerContext, bindingContext)) {
string prefix = CreateSubPropertyPrefix(bindingContext.ModelName, descriptor.Name);
ModelBindingContext context = new ModelBindingContext {
ModelMetadata = bindingContext.PropertyMetadata[descriptor.Name],
ModelName = prefix,
ModelState = bindingContext.ModelState,
ValueProvider = bindingContext.ValueProvider
};
IModelBinder propertyBinder = Binders.GetBinder(descriptor.PropertyType);
object value = GetPropertyValue(controllerContext, context, descriptor, propertyBinder);
// Great, I've set the descriptor, but how do I set the actual model property?
descriptor.SetValue(context.Model, value);
}
return model;
}
}
我是否覆盖了正确的方法?针对我给定的问题实施解决方案的正确方法是什么?
希望我的情况和问题是清楚的。如果我可以进一步详细说明来回答任何问题,我会:)
我明白了。我离得不远。
为了循环访问一个对象 PropertyDescriptor
,您需要从现有的绑定上下文中创建一个新的 ModelBindingContext
,并将其传递给您覆盖的方法。这是一个片段,您可以在下面找到完整的答案。
GenericModel model = new ModelBindingContext {
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(GenericModel)),
ValueProvider = bindingContext.ValueProvider,
ModelName = bindingContext.ModelName
};
当您遍历属性时,您为每个要迭代的 属性 创建一个新的 ModelBindingContext
,获取 IModelBinder
,获取 属性 值,最后设置 属性 值,这会滋润您为 ModelBindingContext
创建的 GenericModel
。
public class DataItemModelBinder: DefaultModelBinder {
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) {
GenericModel model = CreateGenricModel(controllerContext, bindingContext);
//this is basically a factory that creates the correct IDataItem
// given the newly hydrated GenericModel. It doesn't really apply
// for the given question/answer so I left it out.
return CreateDataItem(model);
}
private IDataItem CreateDataItem(GenericModel genericModel) {
IModelRetriever retriever = GetRetreiver(genericModel.DataType);
return retriever.GetModel(genericModel);
}
private GenericModel CreateGenricModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
GenericModel model = new GenericModel();
ModelBindingContext context = CreateGenericFieldBindingContext(bindingContext, model);
foreach (PropertyDescriptor descriptor in GetFilteredModelProperties(controllerContext, context)) {
string prefix = CreateSubPropertyPrefix(context.ModelName, descriptor.Name);
ModelBindingContext propertyContext = new ModelBindingContext {
ModelMetadata = context.PropertyMetadata[descriptor.Name],
ModelName = prefix,
ValueProvider = context.ValueProvider
};
IModelBinder propertyBinder = Binders.GetBinder(descriptor.PropertyType);
object value = GetPropertyValue(controllerContext, propertyContext, descriptor, propertyBinder);
SetProperty(controllerContext, context, descriptor, value);
}
return model;
}
private ModelBindingContext CreateGenericFieldBindingContext(ModelBindingContext bindingContext, GenericModel model) {
return new ModelBindingContext {
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(GenericModel)),
ValueProvider = bindingContext.ValueProvider,
ModelName = bindingContext.ModelName
};
}
private string CreateSubPropertyPrefix(string prefix, string propertyName) {
if (string.IsNullOrEmpty(prefix))
return propertyName;
if (string.IsNullOrEmpty(propertyName))
return prefix;
return (prefix + "." + propertyName);
}
}
我有一个自定义模型活页夹,可以将可以反序列化为 IEnumerable<GenericModel>
的请求转换为操作参数 IEnumerable<IDataItem>
。我无法弄清楚要从 DefaultModelBinder
覆盖哪个方法,如何实现它,以及它到底应该做什么 return。
最近几天通过谷歌进行梳理并找到了大量信息,我仍然没有很好地理解虚拟方法 BindModel vs. CreateModel vs. OnModelUpdated vs. 等的明确目的
请求是 json 个 GenericModel
数组,需要将其转换为 IEnumerable<IDataItem>
以执行此操作...
public ActionResult Save(IEnumerable<IDataItem> models)
我的模型活页夹(继承自 DefaultModelBinder
)正在实施 CreateModel
并尝试手动将 bindingContext.ModelType
从 IDataItem
设置为 GenericModel
以便我可以为每个 属性 正确获取 PropertyDescriptor
,获取其值,并为 GenericModel
实例设置属性。一旦我有一个水合的 GenericModel
我会创建正确的混凝土 IDataItem
和 return 它回到 DefaultModelBinder
继续它的绑定和验证(我需要因为一些属性IDataItem
装饰有 ValidationAttributes
).
问题是我永远无法弄清楚如何正确地遍历 GenericModel
属性并从 ValueProvider
设置它的值。
我的想法是我可以将 bindingContext.ModelType
从 IDataItem
更改为 GenericModel
并遍历它的 PropertyDescriptors
,但是 bindingContext.ModelType
没有a setter,虽然它似乎源自 bindingContext.ModelMetadata.Model
,但更改无效 - bindingContext.ModelType
仍然是 IDataItem
.
public class DataItemModelBinder : DefaultModelBinder {
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) {
GenericModel model = CreateGenericModel(controllerContext, bindingContext);
// just return a dummy instance of IDataItem so I can debug
return new TextboxModel();
}
private GenericModel CreateGenericModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
GenericModel model = new GenericModel();
// I was thinking this would change bindingContext.ModelType but it doesn't
bindingContext.ModelMetadata.Model = model;
foreach (PropertyDescriptor descriptor in GetFilteredModelProperties(controllerContext, bindingContext)) {
string prefix = CreateSubPropertyPrefix(bindingContext.ModelName, descriptor.Name);
ModelBindingContext context = new ModelBindingContext {
ModelMetadata = bindingContext.PropertyMetadata[descriptor.Name],
ModelName = prefix,
ModelState = bindingContext.ModelState,
ValueProvider = bindingContext.ValueProvider
};
IModelBinder propertyBinder = Binders.GetBinder(descriptor.PropertyType);
object value = GetPropertyValue(controllerContext, context, descriptor, propertyBinder);
// Great, I've set the descriptor, but how do I set the actual model property?
descriptor.SetValue(context.Model, value);
}
return model;
}
}
我是否覆盖了正确的方法?针对我给定的问题实施解决方案的正确方法是什么?
希望我的情况和问题是清楚的。如果我可以进一步详细说明来回答任何问题,我会:)
我明白了。我离得不远。
为了循环访问一个对象 PropertyDescriptor
,您需要从现有的绑定上下文中创建一个新的 ModelBindingContext
,并将其传递给您覆盖的方法。这是一个片段,您可以在下面找到完整的答案。
GenericModel model = new ModelBindingContext {
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(GenericModel)),
ValueProvider = bindingContext.ValueProvider,
ModelName = bindingContext.ModelName
};
当您遍历属性时,您为每个要迭代的 属性 创建一个新的 ModelBindingContext
,获取 IModelBinder
,获取 属性 值,最后设置 属性 值,这会滋润您为 ModelBindingContext
创建的 GenericModel
。
public class DataItemModelBinder: DefaultModelBinder {
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) {
GenericModel model = CreateGenricModel(controllerContext, bindingContext);
//this is basically a factory that creates the correct IDataItem
// given the newly hydrated GenericModel. It doesn't really apply
// for the given question/answer so I left it out.
return CreateDataItem(model);
}
private IDataItem CreateDataItem(GenericModel genericModel) {
IModelRetriever retriever = GetRetreiver(genericModel.DataType);
return retriever.GetModel(genericModel);
}
private GenericModel CreateGenricModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
GenericModel model = new GenericModel();
ModelBindingContext context = CreateGenericFieldBindingContext(bindingContext, model);
foreach (PropertyDescriptor descriptor in GetFilteredModelProperties(controllerContext, context)) {
string prefix = CreateSubPropertyPrefix(context.ModelName, descriptor.Name);
ModelBindingContext propertyContext = new ModelBindingContext {
ModelMetadata = context.PropertyMetadata[descriptor.Name],
ModelName = prefix,
ValueProvider = context.ValueProvider
};
IModelBinder propertyBinder = Binders.GetBinder(descriptor.PropertyType);
object value = GetPropertyValue(controllerContext, propertyContext, descriptor, propertyBinder);
SetProperty(controllerContext, context, descriptor, value);
}
return model;
}
private ModelBindingContext CreateGenericFieldBindingContext(ModelBindingContext bindingContext, GenericModel model) {
return new ModelBindingContext {
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(GenericModel)),
ValueProvider = bindingContext.ValueProvider,
ModelName = bindingContext.ModelName
};
}
private string CreateSubPropertyPrefix(string prefix, string propertyName) {
if (string.IsNullOrEmpty(prefix))
return propertyName;
if (string.IsNullOrEmpty(propertyName))
return prefix;
return (prefix + "." + propertyName);
}
}