使用默认模型绑定器使用反射创建的对象的ASP.NET MVC问题

时间:2009-09-24 01:07:17

标签: asp.net-mvc reflection modelbinders updatemodel defaultmodelbinder

我在ASP.NET MVC中遇到一个奇怪的问题,当传递 formCollection 时,对象没有使用 UpdateModel 进行更新。当通过反射创建更新对象时, UpdateModel 似乎无法正常工作。

场景:我有一个应用程序,它有大约50个查找表 - 每个查询表包含完全相同的模式,包括id,title,description,isactive和createdon等典型字段。我希望有一个可以显示所有查找表中数据的视图,而不是构建50个视图。我创建了一个名为IReferenceEntity的接口,并在代表我的查找表的每个POCO中实现它。

使用此界面,我可以使用查找表中的记录轻松填充视图。 (我通过以下内容将项目传递给视图。)

System.Web.Mvc.ViewPage<MyNamespece.IReferenceEntity> 

从数据库到视图,每件事情都很完美。

但是,当我尝试在帖子上更新模型时,我遇到了一些问题。

如果我明确地声明了如下所示的对象引用,那么每个东西都可以完美地工作,并且我的对象的值会使用我的表单中的值进行更新。因此,我可以更新数据库。

AccountStatus a = new AccountStatus();

UpdateModel(a, formCollection.ToValueProvider());

不幸的是,对对象类型进行硬编码会完全破坏使用接口的原因。

(该应用程序的主要目标是能够动态添加新表,例如查找表,而无需执行任何“特殊”操作。这是通过反映加载的程序集并查找实现特定接口的任何类来实现的。或基类)

我的策略是在回发时确定对象的具体类型,然后通过反射创建该类型的实例。 (我用来确定类型的机制有点原始。我把它作为表单中的隐藏字段包含在内。欢迎更好的想法。)

当我通过以下任何一种方法使用反射创建对象的实例时,UpdateModel都没有更新任何对象。

Type t = {Magically Determined Type}

object b = Activator.CreatorInstance(t);

UpdateModel(b, formCollection.ToValueProvider());


Type t = {Magically Determined Type}

var c = Activator.CreatorInstance(t);

UpdateModel(c, formCollection.ToValueProvider());


Type t = {Magically Determined Type}

IReferenceEntity d = Activator.CreatorInstance(t);

UpdateModel(d, formCollection.ToValueProvider());

注意:我已经验证通过relection创建的对象都是正确的类型。

有谁知道为什么会这样?我有点难过。

如果我真的“努力”,我可以创建工厂对象,这将很多实例化这些参考实体/查找对象中的任何一个。但是,这会破坏应用程序允许新的查找表被透明地添加和发现的能力,并且不会那么干净。

另外,我可以尝试从实际的ReferenceEntity基类派生而不是接口,但我怀疑这是否会有所不同。问题似乎是在模型绑定器中使用反射创建的对象。

感谢任何帮助。

安东尼

3 个答案:

答案 0 :(得分:1)

Augi在ASP.NET论坛上回答了这个问题。它只使用了几个小修改。谢谢Augi。


问题是[Try] UpdateModel方法只允许使用泛型参数指定模型类型,因此它们不允许动态模型类型规范。我为此创建了发行​​票。

您可以在此处查看TryModelUpdate方法实现。因此编写自己的重载并不困难:

public virtual bool TryUpdateModelDynamic<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IDictionary<string, ValueProviderResult> valueProvider) where TModel : class
{
    if (model == null)
    {
        throw new ArgumentNullException("model");
    }
    if (valueProvider == null)
    {
        throw new ArgumentNullException("valueProvider");
    }


    //Predicate<string> propertyFilter = propertyName => BindAttribute.IsPropertyAllowed(propertyName, includeProperties, excludeProperties);  
    IModelBinder binder = Binders.GetBinder( /*typeof(TModel)*/model.GetType());

    ModelBindingContext bindingContext = new ModelBindingContext()
                                             {
                                                 Model = model,
                                                 ModelName = prefix,
                                                 ModelState = ModelState,
                                                 //ModelType = typeof(TModel), // old  
                                                 ModelType = model.GetType(),
                                                 // new  
                                                 //PropertyFilter = propertyFilter,  
                                                 ValueProvider = valueProvider
                                             };
    binder.BindModel(ControllerContext, bindingContext);
    return ModelState.IsValid;
}

答案 1 :(得分:0)

您的IReferenceEntity是否包含属性和getter的setter?我认为如果接口有属性设置器,最后一个示例将起作用,尽管你必须将其转换为编译它。

Type t = {Magically Determined Type}

IReferenceEntity d = Activator.CreatorInstance(t) as IReferenceEntity;

UpdateModel(d, formCollection.ToValueProvider());

通常,它不会在类上设置属性的原因是因为它找不到可通过反射使用的公共setter方法。

答案 2 :(得分:0)

快速“尝试另一件事”:

UpdateModel(d as IReferenceEntity, formCollection.ToValueProvider());

不确定这是否有效,我自己没有尝试过,但这是我想到的第一件事。

如果我稍后有机会,我会偷看默认模型Binder代码,看看那里有什么东西是显而易见的......