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

发布于 2024-08-05 20:37:37 字数 1705 浏览 3 评论 0原文

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

场景:我有一个应用程序,其中大约有 50 个查找表,每个表都包含完全相同的架构,包括 id、title、description、isactive 和 createon 等典型字段。我不想构建 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());

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

有谁知道为什么会发生这种情况?我有点难住了。

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

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

任何帮助表示赞赏。

安东尼

I am having a weird issue in ASP.NET MVC with objects not being updated with UpdateModel when passed a formCollection. UpdateModel does not appear to be working properly when the object being updated is created through reflection.

Scenario: I have an application which has approximately 50 lookup tables--each of which includes exactly the same schema including typical fields like id, title, description, isactive, and createdon. Rather than build 50 views, I wanted to have a single view which could display the data from all of the lookup tables. I created an Interface called IReferenceEntity and implemented it in each of the POCOs representing my lookup tables.

Using this interface, I am able to easily populate a view with a record from the lookup table. (I pass the items to the view via the following.)

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

From the database to the view, every thing works perfectly.

However, when I attempt to update the model on post, I am running into some problems.

If I explicitly declare an object reference like the following, every thing works perfectly and the values of my object are updated with the values from my form. Hence, I can then update the database.

AccountStatus a = new AccountStatus();

UpdateModel(a, formCollection.ToValueProvider());

Unfortunately, hard coding the object type would completely defeat the reason for using an interface.

(A primary objective of the application is to be able to dynamically add new tables such as lookup tables without having to do anything "special". This is accomplished by reflecting on the loaded assemblies and locating any classes which implement a specific interface or base class)

My strategy is to determine the concrete type of the object at postback and then create an instance of the type through reflection. (The mechanism I use to determine type is somewhat primitive. I include it as a hidden field within the form. Better ideas are welcome.)

When I create an instance of the object using reflection through any of the following methods, none of the objects are being updated by 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());

Note: I have verified that the objects which are being created through relection are all of the proper type.

Does anyone have any idea why this might be happening? I am somewhat stumped.

If I was really "hard up", I could create factory object which would many instantiate any one of these reference entity/lookup objects. However, this would break the application's ability to allow for new lookup tables to be added and discovered transparently and is just not quite as clean.

Also, I could try deriving from an actual ReferenceEntity base class as opposed to an interface, but I am doubtful whether this would make any difference. The issue appears to be with using reflection created objects in the modelbinder.

Any help is appreciated.

Anthony

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

神妖 2024-08-12 20:37:37

Augi 在 ASP.NET 论坛上回答了这个问题。只需进行一些小的修改即可使用。谢谢奥吉。


问题是 [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;
}

Augi answered this on ASP.NET forums. It worked with only a couple of minor modifications. Thank you Augi.


The problem is that [Try]UpdateModel methods allow to specify model type using generic parameter only so they don't allow dynamic model type specification. I have created issue ticket for this.

You can see TryModelUpdate method implementation here. So it's not difficult to write own overload:

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;
}
转身以后 2024-08-12 20:37:37

您的 IReferenceEntity 是否包含属性的 setter 和 getter?我认为如果接口有属性设置器,最后一个示例就会起作用,尽管您必须对其进行强制转换才能编译。

Type t = {Magically Determined Type}

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

UpdateModel(d, formCollection.ToValueProvider());

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

Does your IReferenceEntity contain setters on the properties as well as getters? I would think that the last sample would work if the interface had property setters, though you'd have to cast it to get it to compile.

Type t = {Magically Determined Type}

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

UpdateModel(d, formCollection.ToValueProvider());

Normally the reason that it won't set a property on a class is because it can't find a public setter method available to use via reflection.

国粹 2024-08-12 20:37:37

只是一个快速的“另一件事要尝试”:

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

不确定这是否有效,我自己也没有尝试过,但这是我首先想到的事情。

如果以后有机会,我会查看默认模型绑定程序代码,看看其中是否有任何明显的内容......

Just a quick "another thing to try":

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

Not sure if that will work, and I haven't tried it myself, but it's the first thing that came to mind.

If I get a chance later I'll peek at the Default Model Binder code and see if there's anything in there that is obvious...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文